面向对象的设计准则_依赖倒转原则
动机
在一个应用程序中,我们有一些实现了基础的、主要的操作的底层类和一些封装了复杂逻辑的上层类。实现这种结构的很自然地方式就是,先编写底层类,完成后再编写复杂的上层类。因为上层类是由其他东西定义的,所以这看起来是一种很合理的方式。但是这不是一个灵活的设计,如果我们需要替换一个底层类时会发生什么?
让我们以经典的拷贝程序为例,它从键盘读取一些字符,然后把他们输出到打印设备上。包含该逻辑的上层类是Copy类,底层类是KeyboardReader和PrinterWriter。
在一个不好的设计中,上层类直接使用底层的类,在这种情况下,如果我们想要把输出定向到新的FileWriter类,就必须修改Copy类的代码(假设它是一个具有很多逻辑的复杂类并且很难测试)。
为了避免这种问题,我们可以在上层类和底层类之间引入一个抽象层。因为上层模块包含复杂的逻辑,所以它们不应该依赖于底层模块,新的抽象层也不应该基于底层模块而创建。底层模块应该基于抽象层而创建。
基于这个原则,设计类结构的方式应该是从上层模块到底层模块。
上层类--->抽象层--->底层类
依赖倒转原则
- 上层模块不应该依赖于底层模块,它们都应该依赖于抽象。
- 抽象不应该依赖于细节,细节应该依赖于抽象。
实例
下面是一个违反了依赖倒转原则的例子。我们有一个上层类Manager和底层类Worker。我们需要在程序中添加一个新模块,因为有新的特殊的工作者被雇用。为此,我们创建一个新的类SuperWorker。
我们假设Manager是一个包含非常复杂的逻辑的类,现在为了引入新的SuperWorker,我们需要修改它。让我们看一下这有哪些缺点:
- 我们需要修改Manager类(记住,它是一个非常复杂的类,这需要一些时间和努力)。
- Manager类的一些现有功能可能会受到影响。
- 需要重做单元测试。
所有的这些问题需要大量的时间去解决。但是如果程序的设计符合依赖倒转原则将会非常简单。意思是我们设计Manager类和一个IWorker接口以及一些实现了该接口的Worker类。当需要添加SuperWorker类时我们只需要让它实现IWorker接口。
- //Dependency Inversion Principle - Bad example
- class Worker {
- public void work() {
- // ....working
- }
- }
- class Manager {
- Worker m_worker;
- public void setWorker(Worker w) {
- m_worker=w;
- }
- public void manage() {
- m_worker.work();
- }
- }
- class SuperWorker {
- public void work() {
- //.... working much more
- }
- }
//Dependency Inversion Principle - Bad example class Worker { public void work() { // ....working } } class Manager { Worker m_worker; public void setWorker(Worker w) { m_worker=w; } public void manage() { m_worker.work(); } } class SuperWorker { public void work() { //.... working much more } }
下面是支持依赖倒转原则的代码。在这个新的设计中,我们增加了一个新的抽象层IWork接口。现在,上面的问题得到了解决:
- 不需要修改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 m_worker;
- public void setWorker(IWorker w) {
- m_worker=w;
- }
- public void manage() {
- m_worker.work();
- }
- }
//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 m_worker; public void setWorker(IWorker w) { m_worker=w; } public void manage() { m_worker.work(); } }
总结
应用该原则意味着上层类不直接使用底层类,他们使用接口作为抽象层。这种情况下上层类中创建底层类的对象的代码不能直接使用new操作符。可以使用一些创建型设计模式,例如工厂方法,抽象工厂和原型模式。
模版设计模式是应用依赖倒转原则的一个例子。
当然,使用该模式需要额外的努力和更复杂的代码,不过可以带来更灵活的设计。不应该随意使用该原则,如果我们有一个类的功能很有可能在将来不会改变,那么我们就不需要使用该原则。