DIP原则的定义是:高层模块不应该依赖于低层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象.
很多时候,一些传统的开发方式比较倾向于创建一些高层模块依赖于低层模块,策略依赖于细节.实际上这些方法的目的之一就是要定义子程序的程序层次结构,以描述该层次的高层模块如何调用低层模块.
考虑下这些高层模块意味着什么?正是高层模块包含了应用程序中重要的策略选择和业务模型.这些高层模块使得所在的应用程序区别于其他.然而,如果这些高层模块依赖于低层模块,那么对低层模块的改动就会直接影响高层模块,从而迫使它们依次做出改动.
此外,我们都希望能够重用高层的策略设置模块.我们已经经常通过子程序库的形式来重用低层模块.如果高层模块依赖低层模块,那么在不同的上下文中重用高层模块就变得非常困难.如果高层模块独立于低层模块,那么高层模块就可以非常容易地被重用.
所有结构良好的面向对象架构都应具有清晰的层次定义,每个层次通过一个定义良好的,受控的接口向外提供一组内聚的服务.对这个描述的简单理解可能至出现类似下图的结构:
这看起来似乎是正确的,但它存在一个隐藏的错误特征,那就是:Policy层对于其下一直到Utility层的改动都是敏感的.依赖关系是传递的.那么来看看下图的结构:
上面展示了一个更为合理的模型.每个较高层次都为它所需要的服务申明了一个抽象接口.较低层次实现了这个抽象接口.每个高层次类都通过该抽象接口使用下一层.这样高层就不依赖于低层,低层反而依赖于在高层次中申明的抽象服务接口.需要注意的是这里的倒置不仅仅是依赖关系的倒置,它也是接口所有权的倒置.通常我们会认为工具库应该有它们自己的接口便于被调用.但是当应用了DIP后,我们发现往往客户程序拥有抽象接口,而它们的服务者则从这些抽象接口派生.
此外,这里所说的所有权是指接口是随它们的客户程序发布的,而不是实现它们的服务器程序.接口和客户程序位于同一个包或者库中,这样就迫使服务器程序库或者包依赖于客户程序库或者包.有时我们会不想让服务器程序依赖于客户程序,特别是当又多份客户程序但是服务器程序却仅有一份时.在这种情况下,客户程序必须得遵循服务接口,并把它发布到一个独立的包中.
简单的解释DIP原则就是依赖于抽象,这个原则建议我们不应该依赖于具体的类,也就是说所有的依赖关系都应该终止于抽象类或者接口.大致有下面三个特征:
任何变量都不应该持有一个指向具体类的引用
任何类都不应该从具体类派生
任何方法都不应该重写它的任何基类中已经实现了的方法
其实在每个程序中多少都有违反该特征的情况,有时候必须要创建具体类的实例,而创建这些实例的模块将会依赖于它们.另外这个特征对于那些虽然具体但是很稳定的类(比如基元类型)来说并不适用.
通常我们程序中所编写的大多数具体类都是不稳定的,如果不想直接依赖于这些类,可以通过把它们隐藏在抽象接口的后面来隔离它们的不稳定性.但是,如果一个不稳定类的接口需要变化时,这个变化一定会影响到表示该类的抽象接口,这种变化破坏了由抽象接口维持的隔离性.
所以如果看得更远一点,认为是由客户模块或者层来申明它们需要的服务接口,那么仅当客户需要时才会对接口进行改变.这样,改变实现抽象接口的类就不会影响到客户