设计模式 之 依赖倒置原则 (Dependency Inversion Principle)
Motivation
动机
When we design software applications we can consider the low level classes the classes which implement basic and primary operations(disk access, network protocols,...) and high level classes the classes which encapsulate complex logic(business flows, ...). The last ones rely on the low level classes. A natural way of implementing such structures would be to write low level classes and once we have them to write the complex high level classes. Since high level classes are defined in terms of others this seems the logical way to do it. But this is not a flexible design. What happens if we need to replace a low level class?
我们设计软件的时候,可以把其分为低层类和高层类,低层类用于实现基础和主要的操作(磁盘访问,网络协议...),高层类用于封装复杂逻辑(业务流...),高层类依赖低层类。实现此类结构自然的方式是先写低层类,然后使用它们来实现复杂的高层类。高层类基于其他类来实现看起来是合乎逻辑的,但是这种设计不够灵活,如果我们要替换低层类怎么办呢?
Let's take the classical example of a copy module which reads characters from the keyboard and writes them to the printer device. The high level class containing the logic is the Copy class. The low level classes are KeyboardReader and PrinterWriter.
我们来举一个经典的例子:一个拷贝模块从键盘读取字符,然后将它们写到输出设备。包含逻辑的高层类是Copy类,低层类是KeyboardReader和PrinterWriter。
In a bad design the high level class uses directly and depends heavily on the low level classes. In such a case if we want to change the design to direct the output to a new FileWriter class we have to make changes in the Copy class. (Let's assume that it is a very complex class, with a lot of logic and really hard to test).
在不好的设计中,高层类直击使用和依赖低层类。这种情况下,如果我们想要更改设计,让其输出到新的FileWriter类时,我们就不得不修改Copy类。(我们假设Copy类是一个复杂类,内部包含很多逻辑,并且难于测试)。
In order to avoid such problems we can introduce an abstraction layer between high level classes and low level classes. Since the high level modules contain the complex logic they should not depend on the low level modules so the new abstraction layer should not be created based on low level modules. Low level modules are to be created based on the abstraction layer.
为了避免这样的问题,我们在高层类和低层类之间引入一个抽象层。因为包含复杂逻辑的高层类不需要依赖低层模块,所以新的抽象层也不需要依赖低层模块创建。低层模块则要基于抽象层创建。
According to this principle the way of designing a class structure is to start from high level modules to the low level modules:
High Level Classes --> Abstraction Layer --> Low Level Classes
根据这个原则设计类结构是从高层模块开始下降到低层模块的。
高层类-->抽象层-->低层类
Intent
目的
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
- 高层模块不应该依赖于低层模块,它们都应该依赖于抽象。
- 抽象不应该依赖细节,细节应该依赖于抽象。
Example
示例
Below is an example which violates the Dependency Inversion Principle. We have the manager class which is a high level class, and the low level class called Worker. We need to add a new module to our application to model the changes in the company structure determined by the employment of new specialized workers. We created a new class SuperWorker for this.
下面是一个违法依赖倒置原则的例子。我们有一个高层类manager,一个低层类Worker。我们以向应用中增加新的模块的形式来模拟新的特殊的workers对公司结构造成的影响。我们为此创建一个新的SuperWorker类。
Let's assume the Manager class is quite complex, containing very complex logic. And now we have to change it in order to introduce the new SuperWorker. Let's see the disadvantages:
- we have to change the Manager class (remember it is a complex one and this will involve time and effort to make the changes).
- some of the current functionality from the manager class might be affected.
- the unit testing should be redone.
假设Manager类相当复杂,包含复杂逻辑。现在为了引入新的SuperWorker类我们不得不修改它,这具有以下缺点:
- 我们不得不修改Manager类(记住它很复杂,需要花费时间和精力来应对改变)。
- manager类的一些当前功能可能会受到影响。
- 单元测试需要重新进行。
All those problems could take a lot of time to be solved and they might induce new errors in the old functionlity. The situation would be different if the application had been designed following the Dependency Inversion Principle. It means we design the manager class, an IWorker interface and the Worker class implementing the IWorker interface. When we need to add the SuperWorker class all we have to do is implement the IWorker interface for it. No additional changes in the existing classes.
这些问题可能需要花费很多时间来解决,并且可能会在原有功能中引用新的错误。如果使用依赖倒置原则设计应用,将不会发生上面提到情况。依照原则,我们需要设计manager类,IWorker接口和实现IWorker接口的Worker类。当我们需要添加新的SuperWorker类时,我们只需要为它实现IWorker接口就可以了,不会对现有的类产生额外的修改。
1 // Dependency Inversion Principle - Bad example 2 3 class Worker { 4 5 public void work() { 6 7 // ....working 8 9 } 10 11 } 12 13 14 15 class Manager { 16 17 Worker worker; 18 19 20 21 public void setWorker(Worker w) { 22 worker = w; 23 } 24 25 public void manage() { 26 worker.work(); 27 } 28 } 29 30 class SuperWorker { 31 public void work() { 32 //.... working much more 33 } 34 }
Below is the code which supports the Dependency Inversion Principle. In this new design a new abstraction layer is added through the IWorker Interface. Now the problems from the above code are solved(considering there is no change in the high level logic):
- Manager class doesn't require changes when adding SuperWorkers.
- Minimized risk to affect old functionality present in Manager class since we don't change it.
- No need to redo the unit testing for Manager class.
下述代码是支持依赖导致原则的。在这个新的设计中,通过IWorker接口添加了新的抽象层。现在上面代码中的问题得到了解决(拷贝在高层逻辑中没有改动):
- 当添加SuperWorkers时Manager类不需要修改。
- 因为我们不修改Manager类,所以将对原有功能的影响风险降到最低。
- 不需要为Manager类重新进行单元测试。
1 // Dependency Inversion Principle - Good example 2 interface IWorker { 3 public void work(); 4 } 5 6 class Worker implements IWorker{ 7 public void work() { 8 // ....working 9 } 10 } 11 12 class SuperWorker implements IWorker{ 13 public void work() { 14 //.... working much more 15 } 16 } 17 18 class Manager { 19 IWorker worker; 20 21 public void setWorker(IWorker w) { 22 worker = w; 23 } 24 25 public void manage() { 26 worker.work(); 27 } 28 }
Conclusion
结论
When this principle is applied it means the high level classes are not working directly with low level classes, they are using interfaces as an abstract layer. In this case instantiation of new low level objects inside the high level classes(if necessary) can not be done using the operator new. Instead, some of the Creational design patterns can be used, such as Factory Method, Abstract Factory, Prototype.
当应用这个原则时,也就意味着高层类不直接依赖低层类工作,它们使用了抽象层接口。这种情况下在高层类中的低层类实例就不能使用操作符new来分配,我们可以使用创建型设计模式来为此提供支持,比如工厂方法,抽象工厂,原型模式。
The Template Design Pattern is an example where the DIP principle is applied.
模板设计模式是依赖倒置原则应用的一个实例。
Of course, using this principle implies an increased effort, will result in more classes and interfaces to maintain, in a few words in more complex code, but more flexible. This principle should not be applied blindly for every class or every module. If we have a class functionality that is more likely to remain unchanged in the future there is not need to apply this principle.
当然,使用这个原则意味着我们要花费更多精力,因为这会引入更多需要维护的类和接口,简而言之此原则通过使用更复杂的代码来换取了更灵活的设计。这个模式不应该在任意类和模块中乱用,如果我们我们的类功能在未来不需要变更,则不需要使用这个原则。
原文地址:https://www.oodesign.com/dependency-inversion-principle.html