OO设计原则 -- Dependency Inversion Principle:依赖倒置原则(DIP)

依赖倒置原则的2个重要方针

A. High level modules should not depend upon low level modules. Both shoulddepend upon abstractions.

高层模块不应该依赖于低层模块,二者都应该依赖于抽象
B. Abstractions should not depend upon details. Details should depend uponabstractions.
抽象不应该依赖于细节,细节应该依赖于抽象

 

概念解说:

依赖:在程序设计中,如果一个模块a使用/调用了另一个模块b,我们称模块a依赖模块b。

低层模块:往往在一个应用程序中,我们有一些低层次的类,这些类实现了一些

基本的或初级的操作,我们称之为低层模块;

高层模块:另外有一些高层次的类,这些类封装了某些复杂的逻辑,并且依赖于

低层次的类,这些类我们称之为高层模块。

 

我们现在来看看依赖有几种,依赖也就是耦合,分为下面三种:

----- 零耦合(Nil Coupling)关系,两个类没有依赖关系,那就是零耦合。

----- 具体耦合(Concrete Coupling)关系,两个具体的类之间有依赖关系,

那么就是具体耦合关系,如果一个具体类直接引用另外一个具体类就会发

生这种关系。

-----抽象耦合(Abstract Coupling)关系,这种关系发生在一个具体类和一个抽

象类之间,这样就使必须发生关系的类之间保持最大的灵活性。


为什么叫做依赖倒置(DependencyInversion)呢?
面向对象程序设计相对于面向过程(结构化)程序设计而言,依赖关系被倒置了。因为传统的结构化程序设计中,高层模块总是依赖于低层模块。

 

依赖倒置(Dependence InversionPrinciple)原则讲的是:要依赖于抽象,不要依赖于具体。

简单的说,依赖倒置原则要求客户端依赖于抽象耦合。原则表述:

抽象不应当依赖于细节;细节应当依赖于抽象;

要针对接口编程,不针对实现编程。

 

问题的提出

Robert C. Martin氏在原文中给出了“Bad Design”的定义:
1. It is hard to change because every change affects too many other parts ofthe system.(Rigidity)
系统很难改变,因为每个改变都会影响其他很多部分。
2. When you make a change, unexpected parts of the system break. (Fragility)
当你对某地方做一修改,系统的看似无关的其他部分都不工作了。
3. It is hard to reuse in another application because it cannot be disentangledfrom the current application. (Immobility)
系统很难被另外一个应用重用,因为你很难将要重用的部分从系统中分离开来。

导致“Bad Design”的很大原因是“高层模块”过分依赖“低层模块”。
一个良好的设计应该是系统的每一部分都是可替换的。
如果“高层模块”过分依赖“低层模块”:

一方面一旦“低层模块”需要替换或者修改,“高层模块”将受到影响;

另一方面,高层模块很难可以重用。

比如,一个Copy模块,需要把来自Keyboard的输入复制到Print,

即使对Keyboard和Print的封装已经做得非常好,但如果Copy模块里直接使用Keyboard与Print,

Copy任很难被其他应用环境(比如需要输出到磁盘时)重用。

问题的解决:

为了解决上述问题,Robert C. Martin氏提出了OO设计的Dependency Inversion Principle (DIP) 原则。

DIP给出了一个解决方案:

在高层模块与低层模块之间,引入一个抽象接口层。


High Level Classes(高层模块)  -->

Abstraction Layer(抽象接口层) -->

Low Level Classes(低层模块)


抽象接口是对低层模块的抽象,低层模块继承或实现该抽象接口。
这样,高层模块不直接依赖低层模块,高层模块与低层模块都依赖抽象接口层。
当然,抽象也不依赖低层模块的实现细节,低层模块依赖(继承或实现)抽象定义。

Robert C. Martin氏给出的DIP方案的类的结构图:


PolicyLayer -->

MechanismInterface(abstract) -->

MechanismLayer -->

UtilityInterface(abstract) -->

UtilityLayer


类与类之间都通过Abstract Layer来组合关系。

 

 实例说明DIP

反面例子:


缺点:耦合太紧密,Light发生变化将影响ToggleSwitch。

解决办法一:
将Light作成Abstract,然后具体类继承自Light。


优点:ToggleSwitch依赖于抽象类Light,具有更高的稳定性,而BulbLight与TubeLight继承自Light,可以根 据"开放-封闭"原则进行扩展。只要Light不发生变化,BulbLight与TubeLight的变化就不会波及ToggleSwitch。

缺点:如果用ToggleSwitch控制一台电视就很困难了。总不能让TV继承自Light吧。

解决方法二:



优点:更为通用、更为稳定。

 

总结

 

 DIP要求客户端依赖于抽象耦合,抽象不应当依赖于细节,细节应当依赖于抽象(Abstractionsshould not depend upon details. Details should depend upon abstractions),这 个原则的另外一个表述就是"四人团"强调的那个:要针对接口编程,不要对实现编程(Program to aninterface, not an implementation),程序在需要引用一个对象时,应当尽可能的使用抽象类型作为变量的静态类型,这就是针对接口编程的含义。DIP是达到"开-闭"原则的途径。

要做到DIP,用抽象方式耦合是关键。由于一个抽象耦合总要涉及具体类从抽象类继承。并且需要保证在任何引用到某类的地方都可以改换成其子类,因此,LSP是DIP的基础。DIP是OOD的核心原则,设计模式的研究和应用都是用它作为指导原则的。DIP虽然强大,但是也很难实现。另外DIP是假定 所有的具体类都会变化,这也不是全对,有些具体类就相当稳定。使用这个类的客户端就完全可以依赖这个具体类而不用再弄一个抽象类。