均为原创,读架构整洁之道的笔记。
依赖反转原则:(DIP :Dependency Inversion Principle)。
如果想要设计一个灵活的系统,再源代码层次的依赖关系中,就应当多引用抽象类型,而非具体实现。
也就是说在静态类型的编程语言中,如Go
,Java
,在引入包或文件时,应当只引入抽象类或接口,而不应当引用任何具体的实现。
但做不到完全引用接口或抽象类,如Java
中的String
,就是一个具体实现,如类似的非常稳定的类,系统原生提供的类,则不在此依赖反转的范围内。我们应当关注自己的业务中,容易变动的模块。
稳定的抽象层
为什么要使用抽象的,而非具体实现呢?
因为每当我们修改接口时,就必然伴随着去修改那些具体的实现。但我们修改具体实现时,却很少修改对应的接口。这就可以认定,接口比实现稳定,如果想要追求架构上的稳定,就必须多使用抽象的,少依赖多变的具体实现。
具体的编码守则:
- 多使用抽象接口,尽可能的避免依赖多变的具体实现。对象的创建也要受到严格限制,通常用抽象工厂来创建对象。
- 不要在具体实现类上,创建衍生类。继承关系是源代码依赖最强最难被修改的。
- 不要覆盖包含具体实现的函数。因为函数内可能会依赖类中的属性,依赖关系太强。
- 避免在代码中写入任何具体实现的相关名字,如写死的类名。或是其他容易变动的事物名字。
工厂模式
如果想要遵守上述守则,就必须要对易变的对象的创建过程做处理,因为在所有的编程语言中,创建对象的过程都避免不了依赖那些对象的具体实现。如你要new
一个对象,那你必须知道那个名字并引入它,就形成了依赖。
一般我们会选择使用抽象工厂模式来解决这个源代码依赖问题。
以那条红色的线为作为区分,作为边界。红线之上为抽象层
(高阶业务层),之下为具体实现层
(具体操作相关细节)。
再看控制方向,Application
是通过控制接口,来获取ConcreteImpl
的。
而具体实现层,是由具体实现来操控具体实现的。控制流
(抽象层)跨越架构的边界
(红线),与源代码(具体实现)跨越该边界
的方向是相反的。
这就是DIP
被称为依赖反转的原因。避开了直接依赖具体实现。
具体实现组件
上图中,可以看到具体实现组件中,还是有依赖关系,ServiceFactoryImpl
依赖ConcreteImpl
。这条依赖关系实际上是违反DIP
的。但这种情况很常见,因为我们不可能完全消除违反DIP
的情况。但是我们必须做到把它们集中到少部分的具体实现组件中,要和高阶业务隔离开来。
如入口main函数
,就必须依赖到一个具体实现,才能开始调用。
本章小结
主要关注那条红色曲线,那是架构边界。