整洁架构SOLID-依赖反转原则(DIP)

定义

依赖反转原则(DIP)主要想告诉我们的是:

如果想要设计一个灵活的系统,在源代码层次的依赖关系中就应该多引用抽象类型,而非具体实现。

也就是说,在Java这类静态类型的编程语言中,在使用use、import、include这些语句时应该只引用那些包含接口、抽象类或者其他抽象类型声明的源文件,不应该引用任何具体实现。

同样的,在Ruby、Python这类动态类型的编程语言中,我们也不应该在源代码层次上引用包含具体实现的模块。当然,在这类语言中,事实上很难清晰界定某个模块是否属于“具体实现”。

String类

显而易见,把这条设计原则当成金科玉律来加以严格执行是不现实的,因为软件系统在实际构造中不可避免地需要依赖到一些具体实现。例如,Java中的String类就是这样一个具体实现,我们将其强迫转化为抽象类是不现实的,而在源代码层次上也无法避免对java.lang.String的依赖,并且也不应该尝试去避免。

String类本身是非常稳定的,因为这个类被修改的情况是非常罕见的,而且可修改的内容也受到严格的控制,所以程序员和软件架构师完全不必担心String类上会发生经常性的或意料之外的修改。

同理,在应用DIP时,我们也不必考虑稳定的操作系统或者平台设施,因为这些系统接口很少会有变动。

我们主要应该关注的是软件系统内部那些会经常变动的(volatile)具体实现模块,这些模块是不停开发的,也就会经常出现变更。

稳定的抽象层

我们每次修改抽象接口的时候,一定也会去修改对应的具体实现。但反过来,当我们修改具体实现时,却很少需要去修改相应的抽象接口。所以我们可以认为接口比实现更稳定。

优秀的软件设计师和架构师会花费很大精力来设计接口,以减少未来对其进行改动。毕竟争取在不修改接口的情况下为软件增加新的功能是软件设计的基础常识。

也就是说,如果想要在软件架构设计上追求稳定,就必须多使用稳定的抽象接口,少依赖多变的具体实现。下面,我们将该设计原则归结为以下几条具体的编码守则:

  • 应在代码中多使用抽象接口,尽量避免使用那些多变的具体实现类。这条守则适用于所有编程语言,无论静态类型语言还是动态类型语言。同时,对象的创建过程也应该受到严格限制,对此,我们通常会选择用抽象工厂(abstract factory)这个设计模式。
  • 不要在具体实现类上创建衍生类。在静态类型的编程语言中,继承关系是所有一切源代码依赖关系中最强的、最难被修改的,所以我们对继承的使用应该格外小心。即使是在稍微便于修改的动态类型语言中,这条守则也应该被认真考虑。
  • 不要覆盖(override)包含具体实现的函数。调用包含具体实现的函数通常就意味着引入了源代码级别的依赖。即使覆盖了这些函数,我们也无法消除这其中的依赖——这些函数继承了那些依赖关系。在这里,控制依赖关系的唯一办法,就是创建一个抽象函数,然后再为该函数提供多种具体实现。
  • 应避免在代码中写入与任何具体实现相关的名字,或者是其他容易变动的事物的名字。这基本上是DIP原则的另外一个表达方式。

工厂模式

如果想要遵守上述编码守则,我们就必须要对那些易变对象的创建过程做一些特殊处理,这样的谨慎是很有必要的,因为基本在所有的编程语言中,创建对象的操作都免不了需要在源代码层次上依赖对象的具体实现。

在大部分面向对象编程语言中,人们都会选择用抽象工厂模式来解决这个源代码依赖的问题。

如下图,描述该设计模式的结构。如你所见,Application类是通过Service接口来使用ConcreteImpl类的。然而,Application类还是必须要构造ConcreteImpl类实例。于是,为了避免在源代码层次上引入对ConcreteImpl类具体实现的依赖,我们现在让Application类去调用ServiceFactory接口的makeSvc方法。这个方法就由ServiceFactoryImpl类来具体提供,它是ServiceFactory的一个衍生类。该方法的具体实现就是初始化一个ConcreteImpl类的实例,并且将其以Service类型返回。

在这里插入图片描述

上图中间的那条曲线代表了软件架构中的抽象层与具体实现层的边界。在这里,所有跨越这条边界源代码级别的依赖关系都应该是单向的,即具体实现层依赖抽象层。

这条曲线将整个系统划分为两部分组件:抽象接口与其具体实现。

  • 抽象接口组件中包含了应用的所有高阶业务规则。
  • 而具体实现组件中则包括了所有这些业务规则所需要做的具体操作及其相关的细节信息。

这里的控制流跨越架构边界的方向与源代码依赖关系跨越该边界的方向正好相反,源代码依赖方向永远是控制流方向的反转——这就是DIP被称为依赖反转原则的原因

具体实现组件

在抽象工厂设计图中具体实现组件的内部仅有一条依赖关系,这条关系其实是违反DIP的。这种情况很常见,我们在软件系统中并不可能完全消除违反DIP的情况。通常只需要把它们集中于少部分的具体实现组件中,将其与系统的其他部分隔离即可

绝大部分系统中都至少存在一个具体实现组件——我们一般称之为main组件,因为它们通常是main函数所在之处。在抽象工厂设计图中,main函数应该负责创建ServiceFactoryImpl实例,并将其赋值给类型为ServiceFactory的全局变量,以便让Application类通过这个全局变量来进行相关调用。

小结

在系统架构图中,DIP通常是最显而易见的组织原则。抽象工厂设计图中的那条曲线称为架构边界,而跨越边界的、朝向抽象层的单向依赖关系则会成为一个设计守则——依赖守则。

参考内容来源于:《架构整洁之道》

相关推荐

  1. SOLID依赖倒置原则

    2024-07-14 18:44:02       69 阅读
  2. Spring的IoC(控制)和 DI依赖注入)

    2024-07-14 18:44:02       36 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-14 18:44:02       50 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-14 18:44:02       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-14 18:44:02       43 阅读
  4. Python语言-面向对象

    2024-07-14 18:44:02       54 阅读

热门阅读

  1. flask基础配置详情

    2024-07-14 18:44:02       15 阅读
  2. 昇思25天学习打卡营第24天|RNN实现情感分类

    2024-07-14 18:44:02       17 阅读
  3. Windows图形界面(GUI)-DLG-C/C++ - 对话框的创建实现

    2024-07-14 18:44:02       16 阅读
  4. 资源分享:红豆地球V1.247 - 免费版

    2024-07-14 18:44:02       18 阅读
  5. 部署运维之一:发展历史

    2024-07-14 18:44:02       16 阅读
  6. 力扣 hot100 -- 多维动态规划

    2024-07-14 18:44:02       17 阅读
  7. SQLite3封装类教程

    2024-07-14 18:44:02       17 阅读
  8. 代码随想录算法训练营第38天

    2024-07-14 18:44:02       25 阅读