六大设计原则之依赖倒置原则(DIP)

很多初学编程的小伙伴在编程时会发现,自己写的类总是频繁的用到(依赖)其他类,一旦被依赖的类需要修改,那么其他的类也统统都要修改一遍,让人感觉烦不胜烦。若是小型的程序也紧紧是觉得烦而已,可一旦是大型的工程,这种强耦合的程序一旦有某一个细节放生改变,那是砸电脑的心都有。

各个具体类之间发生了直接的依赖关系,使得这些类紧紧地耦合在了一起,从而降低了程序的稳定性、可维护性和可读性。要解决这个问题,我们可以用一些方法来将这些程序解耦,降低其耦合性。这种方法就是我即将要讲到的依赖倒置原则(Dependence Inversion Principle ,DIP)

定义

High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.

高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。

也就是说,高层模块、低层模块、细节都应该依赖抽象

  • 低层模块:组成一个大逻辑的颗粒原子逻辑
  • 高层模块:颗粒原子逻辑组成的模块。
  • 抽象:指接口或抽象类,两者都不能被实例化。
  • 细节:实现类,实现接口或继承抽象类而产生的类就是细节,其特点就是可以直接被实例化。

java的精髓

依赖倒置原则在java中的三种含义:

  1. 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
  2. 接口或抽象类不依赖于实现类;
  3. 实现类依赖接口或抽象类。

这三种含义在java的精髓之一——面向接口编程中体现得淋漓尽致。

用与不用依赖倒置原则的对比

既然一开始就说到不用依赖倒置原则有多糟糕,那么下面我们就用一个简单的程序来证明一下。

不使用依赖倒置原则:

程序1

/**
* 大众汽车类
* @author 叶汉伟
*/
public class DaZhong {
    public void run(){
        System.out.println("开大众汽车");
    }
}

/**
 * 司机类
 * @author 叶汉伟
 */
public class Driver {
    public void drive(DaZhong daZhong){
        daZhong.run();
    }
}

public class Client {
    public static void main(String[] args){
        Driver Tom=new Driver();
        DaZhong daZhong=new DaZhong();
        Tom.drive(daZhong);
    }
}

看上去没什么嘛,这不是好好的吗?好像也是哦。那么既然司机会开大众汽车,那应该会开宝马吧。我们让它开一下宝马试试。

先生产一辆宝马给他:

程序2

/**
 * 宝马车类
 * @author 叶汉伟
 */
public class BaoMa {
    public void run(){
        System.out.println("开宝马车");
    }
}

当我们要让司机开宝马的时候,他确开不了,程序报错了。感情他考的是大众驾照,有宝马都开不了啊。

其实要让司机开宝马车也容易,把司机类开车方法的参数改一下就成了呗。的确可以。但又想想,现在知识多了个宝马车,如果我再多个奔驰、本田什么的,那不就是要经常改,大改特改?对于大型的项目来说,这是致命的。

使用依赖倒置原则

那么接下来,我们看一下使用依赖倒置原则有什么优势。

程序3

/**
 * 车子接口
 * @author 叶汉伟
 */
public interface ICar {
    public void run();
}

/**
 * 大众汽车类
 * @author 叶汉伟
 */
public class DaZhong implements ICar{
    public void run(){
        System.out.println("开大众汽车");
    }
}

/**
 * 宝马车类
 * @author 叶汉伟
 */
public class BaoMa implements ICar{
    public void run(){
        System.out.println("开宝马车");
    }
}

/**
 * 司机接口
 * @author 叶汉伟
 */
public interface IDriver {
    public void drive(ICar car);
}

/**
 * 司机类
 * @author 叶汉伟
 */
public class Driver implements IDriver{
    public void drive(ICar car){
        car.run();
    }
}

public class Client {
    public static void main(String[] args){
        IDriver Tom=new Driver();
        //Tom开大众汽车
        ICar daZhong=new DaZhong();
        Tom.drive(daZhong);
        //Tom开宝马
        ICar baoMa=new BaoMa();
        Tom.drive(baoMa);
    }
}

看,现在不仅可以开大众,而且可以开宝马了。要还有什么本田、奔驰汽车,一个drive方法都能开,而开其他的车只需要修改一下客户端就可以了。

上面的程序在实现类之间不发生依赖关系,他们的依赖关系是在接口处发生的,这样就可以大大的降低了类之间的耦合。其实这里不仅用到了依赖倒置原则,还用到了里氏替换原则,从类Client中可以看出来。

实现依赖的三种方法

对象的依赖关系可以通过三种方法来实现:

  1. 接口声明依赖对象
  2. 构造函数传递依赖对象
  3. setter方法传递依赖对象

接口声明依赖对象

在接口处就声明了依赖的对象。如程序3中的司机接口IDriver,其方法drive()的形参是ICar类型的。那么我们可以说IDrive与ICar放生了依赖关系,这个依赖对象在接口处已经声明了。接口声明依赖的方法也叫接口注入

构造函数传递依赖对象

在类中通过构造函数声明依赖对象。具体实现如下:

程序4

/**
 * 司机接口
 * @author 叶汉伟
 */
public interface IDriver {
    public void drive();
}

/**
 * 司机类
 * @author 叶汉伟
 */
public class Driver implements IDriver{
private ICar car;
//通过构造函数注入依赖对象
public Driver(ICar car){
    this.car=car;
}
    public void drive(){
        this.car.run();
    }
}

如果我们想要司机开宝马车,只需要将宝马车对象传入构造函数即可。这种方法又叫做构造函数注入

setter方法传递依赖对象

这种方法通过在抽象中增加一个setter方法实现。具体实现如下:

程序5

/**
 * 司机接口
 * @author 叶汉伟
 */
public interface IDriver {
public void setCar(ICar car);
    public void drive();
}

/**
 * 司机类
 * @author 叶汉伟
 */
public class Driver implements IDriver{
private ICar car;
//setter方法传递依赖对象
public void setCar(ICar car){
    this.car=car;
}
    public void drive(){
        this.car.run();
    }
}

若要司机开那种车,只需要将车子的对象通过Driver对象的setCar()方法传入即可。这种方法又叫setter依赖注入

总结

通过依赖倒置原则,我们可以实现模块中的松耦合。具体来说总结为一下几点规则:

  • 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备
  • 变量的表面类型尽量是接口或者是抽象类
  • 任何类都不应该从具体类派生
  • 尽量不要覆写基类的方法
  • 结合里氏替换原则使用

还没有形成面向接口编程的java学习者们,赶紧将这个规则用起来吧,用过一段之间,你会感觉水平飙升,在代码间游走更加柔韧有余。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值