第4章 依赖倒置原则(DIP)

一、定义

1、模块间要依赖抽象,不要通过具体的实现类。依赖关系通过接口(抽象)进行编程,这就降低客户与实现模块间的耦

合。(接口或抽象类不依赖于实现类,实现类依赖接口或抽象类 面向接口编程OOD) 包含三层含义:

(1)高层模块不应依赖于低层模块,两者都应该依赖其抽象

(2)抽象不应该依赖细节 (3)细节应该依赖于抽象

2、高层模块和底层模块的概念

(1)低层模块:每个逻辑的实现都是原子逻辑组成,不可分割的原子逻辑就是低层模块。一般和具体的实现相关。

(2)高层模块:原子模块再组装就是高层模块,一般和业务逻辑相关。如客户端。

Java语言中,抽象就是指的是接口或抽象类,两者都不能直接被实例化。

细节就是实现类,实现接口或继承抽象类而产生的类就是细节,特点是直接可以被实例化。

3、何为"倒置"

(1)、依赖正置:类间的依赖是实实在在的实现类间的依赖,面向实现编程,符合人类正常的思维。

(2)、依赖倒置:编程就是对现实世界事务进行抽象,然后根据系统设计产生对抽象的依赖,代替事物间的依赖,称

为"倒置"。

二、依赖实现编程存在的问题及改进

1、Driver只能开奔驰车!

Client是场景类

实验:依赖于实现类,导致司机只能开奔驰车

//面向对象设计原则:DIP依赖倒置原则
//司机只能开奔驰车——依赖具体实现

#include <stdio.h>

//奔驰车类
class Benz
{
public:
    void run()
    {
        printf("Benz Runing...\n");
    }
};

//司机类
class Driver
{
public:
    //司机类不是依赖于抽象,而是依赖具体的汽车Benz,
    //导致司机只能开奔驰,不能开其它车的尴尬!
    void drive(Benz& benz)
    {
        benz.run();
    }
 
};

int main()
{
    Driver zhangSan;
    Benz benz;
    
    //张三开奔驰车
    zhangSan.drive(benz); //参数为Benz类型,张三只会开奔驰!
    
    return 0;
}
有车、有司机,在场景类产生相应的对象。到目前为止,这个司机开奔驰车的项目没有问题。但是业务需求变更永无

休止,技术前进就永无止境,在发生变更的时候才发觉我们设计的程序是否是松耦合。

现在呢,张三司机又想开宝马车,怎么实现呢?设计出现问题:司机类和奔驰车类之间是紧耦合的关系,导致的结果

就是系统的可维护性降低,可读性降低,稳定性低。

2、依赖倒置隆重登场

(1)、好处:减少类间耦合,提高系统的稳定性,提高代码的可维护性和可读性,降低并行开发引起的风险

(2)、通过IDriver和ICar两个接口来实现类间的耦合,引入依赖倒置原则。

接口只是一个抽象化的概念,是对一类事物抽象的描述,具体的代码由相应的实现类来实现。

(3)汽车提供run方法。司机的只能就是驾驶汽车,必须实现Driver方法。新增汽车类只需要实现ICar接口。

(4)ICar接口实现抽象之间的依赖关系,Driver实现类也传入ICar接口,到底是那个Car,需要在高层模块声明。

(5)业务场景类中,抽象不应该依赖细节,也就是抽象(接口)不依赖于实现类(细节),高层模块应用的都是抽象。


编程:司机可以开各种车

//面向对象设计原则:DIP依赖倒置原则
//司机可开任何汽车——依赖抽象/接口

#include <stdio.h>

//汽车接口
class ICar
{
public:
    virtual void run() = 0;
};

//奔驰车类
class Benz : public ICar
{
public:
    void run(){printf("Benz runing...\n");}
};

//宝马车类
class BWM : public ICar
{
public:
    void run(){printf("BWM runing...\n");}
};

//司机接口
class IDriver
{
public:
    //是司机应该会驾驶汽车
    virtual void drive(ICar& car) = 0; //依赖接口
};

//司机类
class Driver : public IDriver
{
public:
    void drive(ICar& car) //实现接口
    {
        car.run();
    }
};

int main()
{
    Driver zhangSan;  //引用(对象)   指针(new)
    Benz benz;
    BWM  bwm;
    
    //张三开奔驰车
    zhangSan.drive(benz); 
    
    //张三开宝马
    zhangSan.drive(bwm);
    
    return 0;
}

Client属于高层业务逻辑,对低层的依赖都建立在抽象上,zhangsan都是以IDriver类型进行操作,屏蔽了细节对抽

象的影响。java中,一个变量可以有两种类型:表面类型和实际类型,表面类型是定义的时候赋予的类型,实际类型

是对象的类型。如zhangsan的表面类型是IDriver,实际类型是Driver。

三、依赖的3种写法

1、构造函数传递依赖对象


2、setter方法传递依赖对象


3、通过接口声明依赖对象(接口注入)


四、最佳实践

依赖倒置原则的本质就是通过抽象(抽象类或接口)使各个类或者模块的实现彼此独立,不相互影响,实现模块间的松

耦合。

1、每个类尽量都有接口或者抽象类,或者两者都有,有了抽象才能依赖倒置

2、声明变量时尽量用接口或抽象类,实例化再使用具体的类。变量的表面类型尽量是接口或者抽象类

3、任何类都不应该从具体的类派生(或者继承具体的类不超过两层)

4、尽量不要覆写基类已经实现的方法。如果基类是抽象类,而且这个方法已经实现了,子类尽量不要覆写。类间依

赖的是抽象,覆写抽象方法,对依赖的稳定性产生影响。

5、结合里氏替换原则对子类进行设计。

接口负责定义public属性和方法,并且声明与其他对象的依赖关系。

抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。

"面向接口编程"基本上是依赖倒置原则的核心。

相关文章

什么是设计模式一套被反复使用、多数人知晓的、经过分类编目...
单一职责原则定义(Single Responsibility Principle,SRP)...
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强...
适配器模式将一个类的接口转换成客户期望的另一个接口,使得...
策略模式定义了一系列算法族,并封装在类中,它们之间可以互...
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,...