什么是依赖倒置?
按照定义来说,它有两条:
- 高层模块不应该依赖于低层模块。两者都应该依赖于抽象。
- 抽象不应该依赖于细节。细节应该取决于抽象。
看定义似乎并不能解决“倒置”的疑问。
谁和谁的依赖倒置了?
定义的第一条说:高层模块不应该依赖于低层模块。两者都应该依赖于抽象。
这并不能解决“倒置”的疑问,因为它说的是该原则的代码层级的设计,它引入了一个抽象的中间层。
看第二句:抽象不应该依赖于细节。细节应该取决于抽象。
这里解释了倒置,从抽象依赖于细节的关系倒置为细节依赖于抽象。
什么是细节,什么是抽象?
在低层模块到高层模块的代码设计中,高层模块的实现依赖于低层模块提供的功能。
业务需求作为抽象的事物依赖于有着细节的功能,只有低层模块完成了,高层模块才能被实现。
随着需求的增长与变化,功能越来越多,高层模块的代码会随着低层模块的变化而被不断地修改,以致越来越难以维护。
是时候改变了,怎么改变呢?
思考一下:
- 需求的实现是可以预见的,只要在需要的时候提供适用的功能即可。
- 这些功能的设定可以由高层模块来决定,由低层模块来实现。
这个时候高层模块就不再依赖低层模块的具体实现了,它只依赖自己对功能的设定。
可是低层模块怎么知道高层模块需要的功能是如何设定的呢?
这需要一个独立的约定,引入中间层就能解决。
看一个小剧场:
高层模块需要文件操作的功能,原来他都是自己干,后来越来越忙,就招人来干;
但是业务不断扩张,找到的人不能胜任工作了,他要么自己亲自下场培训要么亲自找人把原来的换掉。
到后来他不胜其烦,直到听说有专门提供人才寻访服务的猎头。
高层模块找到猎头说:我要文件操作的功能,帮帮我;
猎头:给我你的要求,我来找人。
高层模块:能读取、写入、覆盖、追加。
猎头:好的!低层模块甲,这个活你能不能做?
低层模块甲:可以。
猎头带着甲找到高层模块,然后交差了。
过来一段时间...
高层模块:当前我们只能进行本地文件操作,你能不能找个会远程文件操作啊?猎头。
猎头:可以
低层模块乙:等待多时了,嘿嘿。
猎头带着乙找到高层模块把甲替换了,然后交差了。
这个时候,高层模块对低层模块只管按照约定来差遣,不需要与之沟通。
当不满意或者希望找一个更有本事的文件操作员的时候,只需要和猎头沟通,由猎头来寻人。
看完小剧场,将其对应到代码中:
在设计业务的时候,高层模块不需要依赖特定的低层模块来设计,将要使用的功能抽离出来,并设定功能的设计标准,也就是由高层模块决定做什么。
而低层模块决定怎么做,它按照高层模块设定的标准来实现具体的功能细节,交由高层模块使用。
在 OOP 中,我们通过接口来定义功能,提通过类实现接口的方式来实现功能的细节。
这个时候即使还没有编写任何提供功能实现的低层模块代码,我们高层模块的代码也能完成。
这样的代码结构,实现了“高层模块不应该依赖于低层模块,两者都应该依赖于抽象”的定义。
而将业务需要的功能从业务代码中抽离出来,成为中间层,实现了从 “业务需求依赖于既定的功能” 到 “业务逻辑与既定功能的实现都依赖于抽象的中间层”的转换,对应了“抽象不应该依赖于细节。细节应该取决于抽象”的定义。
从抽象依赖具体到具体依赖抽象才是“依赖倒置”的真实含义,而不仅仅是原先的高层模块与低层模块之间依赖关系的变化。