Dependency Inversion Principle (DIP) - OO设计的依赖倒置原则
该文提出了依赖倒置原则的2个重要方针:
A. High level modules should not depend upon low level modules. Both should depend upon abstractions.
B. Abstractions should not depend upon details. Details should depend upon abstractions.
中文意思为:
A. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
B. 抽象不应该依赖于细节,细节应该依赖于抽象
依赖:在程序设计中,如果一个模块a使用/调用了另一个模块b,我们称模块a依赖模块b。
高层模块与低层模块:往往在一个应用程序中,我们有一些低层次的类,这些类实现了一些基本的或初级的操作,我们称之为低层模块;另外有一些高层次的类,这些类封装了某些复杂的逻辑,并且依赖于低层次的类,这些类我们称之为高层模块。
为什么叫做依赖倒置(Dependency Inversion)呢?
面向对象程序设计相对于面向过程(结构化)程序设计而言,依赖关系被倒置了。因为传统的结构化程序设计中,高层模块总是依赖于低层模块。
Robert C. Martin氏在原文中给出了“Bad Design”的定义:
1. It is hard to change because every change affects too many other parts of the 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 disentangled from
the current application. (Immobility)
系统很难被另外一个应用重用,因为你很难将要重用的部分从系统中分离开来。
导致“Bad Design”的很大原因是“高层模块”过分依赖“低层模块”。
一个良好的设计应该是系统的每一部分都是可替换的。
如果“高层模块”过分依赖“低层模块”,一方面一旦“低层模块”需要替换或者修改,“高层模块”将受到影响;另一方面,高层模块很难可以重用。
比如,一个Copy模块,需要把来自Keyboard的输入复制到Print,即使对Keyboard和Print的封装已经做得非常好,但如果Copy模块里直接使用Keyboard与Print,Copy任很难被其他应用环境(比如需要输出到磁盘时)重用。
DIP给出了一个解决方案:在高层模块与低层模块之间,引入一个抽象接口层。
High Level Classes(高层模块) --> Abstraction Layer(抽象接口层) --> Low Level Classes(低层模块)
抽象接口是对低层模块的抽象,低层模块继承或实现该抽象接口。
这样,高层模块不直接依赖低层模块,高层模块与低层模块都依赖抽象接口层。
当然,抽象也不依赖低层模块的实现细节,低层模块依赖(继承或实现)抽象定义。
Robert C. Martin氏给出的DIP方案的类的结构图:
PolicyLayer-->MechanismInterface(abstract)--MechanismLayer-->UtilityInterface(abstract)--UtilityLayer
类与类之间都通过Abstract Layer来组合关系。