1.2 依赖反转原则
在模块化或组件化软件设计中,不同模块间保持松耦合。每个模块都定义有清晰的接口,模块间的调用都通过和限于接口,只要接口不变,模块内对接口的实现可以自由修改和演化。这个原则就是著名的“针对接口编程,而不是针对实现”(Program to an interface, not an implementation)。针对接口编程比使用接口编程更具雄心、野心和企图心(More ambitious),它要更彻底地消除依赖关系。1.1节的DeclareAndReturnInterface方法,虽然dict变量声明为IDictionary接口,但因为初始化为Hashtable类型,该方法所在的类UseInterfaceDemo仍然依赖Hashtable类。要消除此依赖,必须做到模块之间完全以接口交流。
为了分析依赖和接口的关系,下面介绍最简单的两个模块的情况。应用模块需要使用工具模块的功能。按照传统的设计,应用模块直接引用工具模块。两者的依赖关系如图1.1所示。
图1.1 传统设计中应用模块和工具模块的关系
这种紧密的耦合限制了应用模块的可用性,它只能和特定的工具模块一同工作,当有更好的或实现其他功能的工具模块时,它也不能替换以利用。为了打破这个约束,可以将应用模块需要的工具模块的功能抽象成一个工具接口,应用模块通过这个接口来使用工具模块,工具模块只要实现这个接口,就能被自由替换。此时假如将工具接口置于应用模块内,因为工具模块要引用该接口,两模块间的依赖关系发生了奇妙的倒转,如图1.2所示。
图1.2 工具接口置于应用模块之内时两模块之间倒转的关系
每个工具模块在开发时都要引用应用模块,当然是不理想的,尤其作为工具模块根本无法知道要使用它们的应用模块可能是什么样的,所以这样反过来的依赖也必须消除。办法就是令工具接口脱离应用模块,成为一个新的独立模块。这样应用模块和工具模块都仅仅引用这个抽象的接口模块。三者的关系如图1.3所示。
图1.3 工具接口独立于应用模块和工具模块时三者的关系
这个最终方案可以用一句话来概括:调用模块不应该依赖被调用模块,两者应该依赖抽象出的接口。这个原则被称为依赖反转(dependency inversion),虽然它不是顾名思义地指上面第二种情况。