Dependency Inversion Principle
动机
我们设计软件应用时,可以将完成基础操作(磁盘访问,网络协议...等)的类称为低层类,将封装复杂逻辑(业务流程...等)的类称为高层类。后者依赖底层类。一种实现这种结构的自然方式是编写底层类,完成后再编写高层类。由于高层类是基于其他类来定义的,这种做法似乎合乎逻辑。不过这个设计不灵活。如果我们替换掉一个低层类,会发生什么?
我们来看看经典的复制模块例子,复制模块从键盘读取字符,然后写入到打印设备中。Copy
类是包含代码逻辑的高层类。KeyboardReader
和 PrinterWriter
是低层类。
在糟糕的设计中,高层类会直接使用并且严重的依赖低层类。在这种情况下,如果我们想改变设计,将输出导向新的 FileWriter
类上,我们将不得不修改Copy
类。(我们假设这是一个超级复杂的类,其中有一堆逻辑并且真地很难测试)。
为了避免这些问题,我们可以在高层类和低层类之间引入一个抽象层。由于高层模块包含复杂的逻辑,它们不能依赖低层模块,所以新引入的抽象层不能基于低层模块构建。而低层模块应基于抽象层创建。
根据此原则,设计类结构的方式则要从高层模块到低层模块: 高层类 --> 抽象层 --> 低层类
目的
高层模块不应依赖低层模块。两则都应该依赖抽象。
抽象不能依赖细节。细节应该依赖抽象。
例子
以下是一个违背了 DIP 的例子 。我们有一个高层类 Manager
和一个底层类 Worker
。 我们需要给应用增加一个新模块来模拟由于雇佣新的熟练工导致的公司组织架构的调整。为此我们建了一个新类 SuperWorker
。
我们假设 Manager
类相当复杂,其中包含特别复杂的逻辑。现在我们为了引入这个新类 SuperWorker
,不得不对其进行修改。我们看看以下几个缺点:
- 我们得修改
Manager
类(记住,它是个复杂类,需要花费一翻时间和功夫来完成这些修改) - 可能会影响
Manager
中现有的功能 - 必须重写单元测试
所有这些问题都需要耗费很多时间,而且可能引入新错误到旧有功能里。如果应用一开始设计的时候就遵循 DIP ,结果可能就不一样了。这通常表示,我们设计管理类成一个 IWorker
接口,并且 Worker
类实现 IWorker
接口。当需要添加一个 SuperWorker
,我们所要做的只是让其实现 IWorker
接口。不需要在现有类中做其他改动。
// Dependency Inversion Principle -- Bad Example
class Worker{
public void work(){
// ... working 干活
}
}
class Manager{
Worker worker;
public void setWorker(Worker w){
this.worker = w;
}
public void manage(){
this.worker.work();
}
}
class SuperWorker{
public void work(){
// ... working much more 干的更多
}
}
以下是遵循 DIP 原则的代码。新设计中,添加了一个新抽象层 IWorker
接口。现在,上面的那些问题就被解决了(不用修改高层逻辑):
- 添加
SuperWorker
的时候不需要修改Manager
- 由于不用修改
Manager
,影响其中现有功能的风险降到最低 - 不用重新编写
Manager
的单元测试
// Dependency Inversion Principle -- Good example
interface IWorker{
public void work();
}
class Worker implements IWorker{
public void work(){
// ... working
}
}
class SuperWorker implements IWorker{
public void work(){
// ... working much more
}
}
class Manager{
IWorker worker;
public void setWorker(IWorker w){
this.worker = w;
}
public void manage(){
this.worker.work();
}
}
总结
当应用这个原则时,高层类不再直接和低层类交互,它们以接口作为抽象层。在这种情况下,在高层类中实例化低层类(如果需要的话)就不能通过 new
操作符来完成了。相反,可以使用一些创建型设计模式,如工厂模式,抽象工厂,原型模式。
模版模式就是 DIP 应用的一个具体例子。
当然,使用这个原则需要你花点力气的,因为它需要你维护更多的类和接口。总之,它会引入更多复杂的代码,但是更加灵活。不要盲目的将此原则应用到所有的类和模块中。如果我们的一个类功能很可能在以后都不会变,那么也就不需要应用此原则了。
上一篇: 开发-关闭原则
下一篇: 接口隔离原则
目录: //www.greatytc.com/p/af861220a6cc