2009年2月4日星期三

解耦合手段之五:Dependency Inversion Principle

通常,我们认为上层模块理所当然的依赖于下层模块的服务,例如业务模块对数据库的依赖,通信模块对网络的依赖。按照这种逻辑,如果要设计一款电视的话,也许会是这样:
class TVSet{
   private Program program = LocateProgramFromRadiobBroadcast();
   void Play() {
       Show(Program);
   }
};
 因为电视依赖于节目源,所以电视自己创建了节目源,或者说自己来定位节目源。在上面的例子中是定位一个无线广播节目。但这样的电视有个问题,它只能播广播节目。生产厂商要对电视进行测试也要依赖于电视台的广播。因此,我们的电视通常都不是这样的,而是专门有接口,由使用者提供节目源:
class TVSet{
   void Play(Program program) {
       Show(Program);
   }
};
这样,不论是测试用的简单信号发生器,还是DVD,电脑都可以通过电视播放了。
这种依赖关系的倒置被称为Dependency Inversion,又被称为控制反转(Inversion of Control)。这种原则又被戏称为“好莱坞原则”——“Don't call us, we'll call you.”。Uncle Bob是这样描述DIP(Dependency Inversion Principle)的:
A. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
B. 抽象不应该依赖于细节,细节应该依赖于抽象。

依赖倒置使得系统的耦合性降低,模块的可重用性提高,并能提高系统的可测性和可用性。尤其是对单元测试有至关重要的作用,因为这样就可以用mock object来代替真实的对象。
实现依赖倒置的技术被称为“依赖注入”(Dependency Injection)。通常,当一个模块需要一种服务的时候,它要么直接持有对此服务的索引,要么通过一个服务定位器(ServiceLocator)来获得对此服务的索引。而采用依赖注入的方法,则是从外界传入对此服务的索引。例如通过传入参数,或者回调函数来设置服务。
Martin Fowler把依赖注入的方法分为三类:
  • Interface injection
  • Setter injection
  • Constructor injection

甚至有很多框架工具可以支持我们方便的在代码中实现Dependency Injection,例如google Guice。

没有评论:

发表评论