依赖倒置原则:

要依赖抽象,不要依赖具体类。

A.高层次的模块不应该依赖于低层次的模块,它们都应该依赖于抽象。

B.抽象不应该依赖于具体,具体应该依赖于抽象。

为避免在OO设计中违反此原则,应尽量遵循(当然不可能完全避免)以下原则:

1,变量不可以持有具体类的引用。
如果使用new,就会持有具体类的引用。可以使用工厂方法来避开这样的做法。

2,不要让类派生自具体类。
如果派生自具体类,就会依赖于具体类。请派生自一个抽象(接囗或抽象类)

3,不要覆盖基类中已实现的方法。
如果覆盖基类已实现的方法,那么你的基类就不是一个真正适合被继承的抽象。基类中已实现的方法,应该由所有的子类共享。

 

 

 

以《Head First设计模式》里例子说明:

常规思路设计的比萨店的代码里,比萨店类PizzaStore会依赖于Pizza类的具体实现类,有多少种Pizza类的具体实现子类,就有多少个依赖。

 
  
  1. public class DependentPizzaStore { 
  2.     public Pizza createPizza(String style, String type) { 
  3.         Pizza pizza = null
  4.         if (style.equals("NY")) { 
  5.             if (type.equals("cheese")) { 
  6.                 pizza = new NYStyleCheesePizza(); 
  7.             } else if (type.equals("veggie")) { 
  8.                 pizza = new NYStyleVeggiePizza(); 
  9.             } else if (type.equals("clam")) { 
  10.                 pizza = new NYStyleClamPizza(); 
  11.             } 
  12.         } else if (style.equals("Chicago")) { 
  13.             //................................... 
  14.         } else { 
  15.             System.out.println("Error: invalid type of pizza"); 
  16.             return null; 
  17.         } 
  18.         pizza.prepare(); 
  19.         pizza.bake(); 
  20.         pizza.cut(); 
  21.         pizza.box(); 
  22.         return pizza; 
  23.     } 

 

而依赖倒置原则就是要打破这种依赖,让PizzaStore依赖于Pizza类,但不依赖于Pizza类的具体实现类。

通过工厂方法模式,Pizza类的具体实现在PizzaStore的具体实现子类里完成,于是解除了PizzaStore类对具体实现类的依赖,变成了PizzaStore和Pizza具体实现类都依赖于抽象类Pizza。

 

 
  
  1. public abstract class PizzaStore { 
  2.   
  3.     abstract Pizza createPizza(String item);//抽象接囗,将Pizza的具体实现交给子类完成
  4.   
  5.     public Pizza orderPizza(String type) { 
  6.         Pizza pizza = createPizza(type); 
  7.         System.out.println("--- Making a " + pizza.getName() + " ---"); 
  8.         pizza.prepare(); 
  9.         pizza.bake(); 
  10.         pizza.cut(); 
  11.         pizza.box(); 
  12.         return pizza; 
  13.     } 
  14. public class ChicagoPizzaStore extends PizzaStore { 
  15.  
  16.     Pizza createPizza(String item) {//具体实现 
  17.             if (item.equals("cheese")) { 
  18.                     return new ChicagoStyleCheesePizza(); 
  19.             } else if (item.equals("veggie")) { 
  20.                     return new ChicagoStyleVeggiePizza(); 
  21.             } else if (item.equals("clam")) { 
  22.                     return new ChicagoStyleClamPizza(); 
  23.             } else return null; 
  24.     } 

 

--------------------------------------------------------------------------------

另一篇网文的关于依赖倒置原则的介绍:

本文译自Robert C. Martin于1996年发表的文章,将分为三部分贴在这里。原文可参看http://www.objectmentor.com/resources/articles/dip.pdf

A.高层次的模块不应该依赖于低层次的模块,它们都应该依赖于抽象。

B.抽象不应该依赖于具体,具体应该依赖于抽象。

也许有人会问为什么要用“倒置”这个词。坦白地讲,这是因为传统的软件开发方法,如结构化的分析和设计,倾向于创建高层模块依赖于低层模块、抽象依赖于具体的软件结构。实际上,这些方法的目标之一就是去定义描述上层模块如何调用低层模块的子程序层次结构。图1是这种层次结构的一个很好的例子。所以,相对于传统的过程化的方法通常所产生的那种依赖结构,一个设计良好的面向对象的程序中的依赖结构就是“被倒置”的。

来看一下那些依赖于低层模块的高层模块的含义。一个应用中的重要策略决定及业务模型正是在这些高层的模块中。也正是这些模型包含着应用的特性。但是,当这些模块依赖于低层模块时,低层模块的修改将会直接影响到它们,迫使它们也去改变。

这种境况是荒谬的。应该是处于高层的模块去迫使那些低层的模块发生改变。应该是处于高层的模块优先于低层的模块。无论如何高层的模块也不应依赖于低层的模块。

而且,我们想能够复用的是高层的模块。通过子程序库的形式,我们已经可以很好地复用低层的模块了。当高层的模块依赖于低层的模块时,这些高层模块就很难在不同的环境中复用。但是,当那些高层模块独立于低层模块时,它们就能很简单地被复用了。这正是位于框架设计的最核心之处的原则。

4.1. 分层

依照Booch2所说,“所有结构良好的面向对象的架构都有着清晰定义的层次,每一层都通过一个定义良好的、受约束的接口来提供一些相关联的服务。”对这句话的直接表面的理解会使设计人员做出类似于图3所示的结构。在这个图中,高层的Policy类使用了低一层的Mechanism;而Mechanism又使用了具体细节层次上的Utility。这也许看上去很好,但是它有一个很阴险的特性:Policy层对从它到Uility层这一路径上的所有变动都是敏感的。“依赖是可传递的”。Policy层依赖于那些依赖于Utility层的东西,所以Policy层就被传递地依赖于Utility层。这是非常不幸的。

 

3 简单的层次

 4展示了一个更好的模型。每一个较低的层都被一个抽象类所表示。实际的层就由这些抽象类分隔开来。每一个处于较高层中的类都通过抽象接口来使用下一层。这样,就没有一层是依赖于其它层的。相反,这些层都依赖于抽象类。不但Policy层到Utility层的传递依赖被打破了,连Policy层到Mechanism层的直接依赖也被打破了。

 

4 抽象的层次

使用这个模型,Policy层不会被Mechanism层或Utility层的修改所影响。而且,Policy层可以复用到任何按照Mechanism层的接口定义的低层模块的环境中。从而,通过倒置依赖关系,我们可以创建一个同时更加灵活、更加健壮、更加可移植的结构。