知识卡片

抽象工厂模式与依赖反转名字的由来:控制流与源码依赖方向相反

结构图卡

内容

遵守[[DIP真正该关注的是易变具体实现及稳定抽象层的四条编码守则]]时有个绕不开的难题:创建对象这个动作,在几乎所有编程语言里都逃不掉在源码层面依赖具体实现类。解法是抽象工厂模式:Application类本该通过Service接口使用ConcreteImpl类,但直接构造ConcreteImpl实例就意味着依赖了它——于是让Application改为调用ServiceFactory接口的makeSvc方法,真正的实例化工作交给ServiceFactory的衍生类ServiceFactoryImpl去做(它内部初始化ConcreteImpl、以Service类型返回),Application自始至终不必在源码里直接提到ConcreteImpl。图中把系统切成抽象接口层和具体实现层,中间那条曲线就是二者的边界,所有跨越这条边界的源代码依赖都必须单向指向抽象层。这里的关键洞察是:控制流跨越这条边界的方向(Application调用ServiceFactoryImpl创建的具体实现、进而调用它完成实际工作)与源代码依赖跨越边界的方向(ConcreteImplServiceFactoryImpl都依赖抽象接口,而非反过来)正好相反——源代码依赖方向永远是控制流方向的反转,这正是”依赖反转原则”名字的由来。具体实现组件内部往往还残留一条违反DIP的依赖(图中main函数直接创建ServiceFactoryImpl并赋值给ServiceFactory类型的全局变量),这种违反无法被完全消除,只能被集中隔离到少数具体实现组件里(几乎每个系统都至少有一个这样的组件,通常就是包含main函数的main组件)——这条抽象层/实现层的边界曲线,在后续章节会被正式称为”架构边界”,跨边界朝向抽象层的单向依赖也会成为一条独立的设计守则:”依赖守则”。

结构图

flowchart LR
    subgraph 抽象层
    Service[Service接口]
    ServiceFactory[ServiceFactory接口]
    end
    subgraph 具体实现层
    ConcreteImpl[ConcreteImpl类]
    ServiceFactoryImpl[ServiceFactoryImpl类]
    Main[main函数]
    end
    Application -->|控制流:调用makeSvc| ServiceFactory
    ServiceFactoryImpl -->|源码依赖:实现接口,方向与控制流相反| ServiceFactory
    ConcreteImpl -->|源码依赖:实现接口| Service
    Application -->|控制流:使用Service| Service
    Main -->|违反DIP的必要例外,创建实例| ServiceFactoryImpl

参考来源

- 位置:《架构整洁之道》第11章《DIP:依赖反转原则》"工厂模式""具体实现组件""本章小结"(源文件:_epub-src/text/part0012_split_005.html) - 结论依据:原文描述Application通过ServiceFactory接口间接创建ConcreteImpl实例以避免直接依赖,指明抽象层与具体实现层的边界曲线两侧控制流方向与源码依赖方向相反、这正是依赖反转原则名字的由来,并说明具体实现组件(如main组件)里违反DIP的依赖无法完全消除、只能集中隔离,直接支撑本卡片的结构图与解释。 - 原始内容:这里的控制流跨越架构边界的方向与源代码依赖关系跨越该边界的方向正好相反,源代码依赖方向永远是控制流方向的反转——这就是DIP被称为依赖反转原则的原因……我们在软件系统中并不可能完全消除违反DIP的情况……通常只需要把它们集中于少部分的具体实现组件中。