系列文章目录
C++高性能优化编程系列
深入理解设计原则系列
深入理解设计模式系列
高级C++并发线程编程
DIP:依赖反转原则
- 系列文章目录
- 1、依赖反转原则的定义和解读
- 2、稳定的抽象层
- 3、依赖倒置原则和控制反转、依赖注入的联系
- 小结
1、依赖反转原则的定义和解读
SOILD原则中最后一个原则依赖反转原则(Dependency Inversion Principle,DIP)是指一种特定的解耦(传统的依赖关系创建在高层次上,而具体的策略设置则应用在低层次的模块上)形式,使得高层次的模块不依赖于低层次的模块的实现细节,依赖关系被颠倒(反转),从而使得低层次模块依赖于高层次模块的需求抽象。可以理解为依赖反转原则是指高层模块不应该依赖于底层模块,二者都应该依赖于抽象。抽象不应该依赖于具体实现,而具体实现应该依赖于抽象。简单来说,就是要依赖于抽象,而不是具体实现。
2、稳定的抽象层
我们每次修改抽象接口的时候,一定会去修改对应的具体实现。但反过来,当我们修改具体实现时,却很少需要修改相应的抽象接口。所以我们可以认为接口比实现更稳定。
的确,优秀的软件设计师和架构师会花费很大精力来设计接口,以减少未来对其进行改动。毕竟争取在不修改接口的情况下为软件增加新的功能是软件设计的基础常识。
也就是说,如果想要在软件架构设计上追求稳定,就必须多使用稳定的抽象类接口,少依赖多变的具体实现。
下面是抽象工厂模式解决源代码依赖的问题,如图1所示来描述一下该设计模式的结构。如你所见,Application类是通过Service接口来使用ConcreteImpl类的。然而,Application类还是必须要构造ConcreteImpl类实例。于是,为了避免在源代码层次上引入对ConcreteImpl类具体实现的依赖,我们现在让Application类去调用ServiceFactory接口的makeSvc方法。这个方法就是由ServiceFactoryImpl类来具体提供,它是ServiceFactory的一个衍生类。该方法的具体实现就是初始化一个ConcreteImpl的实例,并且将其以Service类型返回。
图1中间的那条曲线代表了软件架构中的抽象层与具体实现层的边界。在这里,所有跨越这条边界源代码级别的依赖关系都应该单向的,即具体实现层依赖抽象层。
这条曲线将整个系统划分为两部分组件:抽象接口与具体实现。抽象接口组件中包含了应用的所有高阶业务规则,而具体实现组件中则包括了所有这些业务规则所需要做的具体操作及其相关的细节信息。
请注意,这里的控制流跨越架构边界的方向与源代码依赖关系跨越该边界的方向正好是相反的,源代码依赖方向永远是控制流方向的反转 - 这就是DIP被称为依赖反转的原因。
3、依赖倒置原则和控制反转、依赖注入的联系
控制反转(Inversion of Control,IoC)是一种实现依赖倒置原则的具体方式。它是通过将对象的创建、组装和管理交给IoC容器来实现的。在传统的程序设计中,我们通常是手动创建对象并直接调用它们的方法,而在IoC中,对象的创建和调用都是由容器来控制的。容器会在运行时动态地创建对象并将它们注入到需要它们的地方。
依赖注入(DI)是一种实现依赖倒置原则的方法,它通过将依赖对象的创建和管理工作交给外部容器(例如 DI 容器)来实现。依赖注入可以通过构造函数注入、属性注入或方法注入的方式来实现。一般来说,依赖注入使得高层模块不需要知道低层模块的具体实现,而只需要定义依赖接口,将依赖对象的创建和管理交给外部容器。依赖注入可以使得系统更加灵活、可扩展和易于测试,使得代码更易于维护和升级。
小结
下面我们将依赖反转原则归结为以下几点具体的编码守则:
- 应在代码中多使用抽象接口,尽量避免使用那些多变的具体实现类。对此,我们通常会选择用抽象工厂这个设计模式。
- 不要在具体实现类上创建衍生类。
- 不要覆盖(override)包含的具体实现函数。
- 应避免在代码中写入与任何具体实现相关的名字,或者是其他容易变动的事物的名字。