手撕设计原则:依赖倒置

用户头像
柳旭
关注
发布于: 2020 年 06 月 15 日
手撕设计原则:依赖倒置

架构0期-W2-实战作业

请描述什么是依赖倒置原则,为什么有时候依赖倒置原则又被称为好莱坞原则?

请描述一个你熟悉的框架,是如何实现依赖倒置原则的。


本篇文章主要描述我对依赖倒置原则的理解。并结合一个我们比较熟悉的框架对依赖倒置原则进行分析。在这个过程中希望我们能够一同学习。

经典设计原则

  • SRP 单一职责原则

就一个类而言,应该仅有一个引起他变化的原因。

  • OCP 开放封闭原则

软件实体(类、对象、模块等)应该是可以扩展的,但是不可修改。

  • LSP 里氏替换原则

子类必须能替换到他们的基本类型。

  • DIP 依赖倒置原则

抽象不应该依赖于细节。细节应该依赖于抽象。

  • ISP 接口隔离原则

不应该强迫客户依赖于他们不用的方法。接口属于客户,不属于他所在的类层次结构。

  • REP 重用发布等价原则

重用的力度就是发布的力度。

  • CCP 共同重用原则

一个包中的所有类应该是共同重用的。如果重用了一个类,就要重用这个包中的所有类。互相之间没有紧密联系的类不应该在一个包中。

  • CRP 共同封闭原则

一个包中的所有类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包有影响,则将对包中所有类产生影响,而对其他的包不产生影响。

  • ADP 无依赖原则

在包的依赖关系中不允许存在环。细节不应该被依赖。

  • SDP 稳定依赖原则

朝着稳定的方向进行依赖。

  • SAP 稳定抽象依赖

一个包的抽象程度应该和其他稳定程度一致。

什么是依赖倒置原则

依赖反转原则的英文翻译是 Dependency Inversion Principle,缩写为 DIP。中文翻译有时候也叫依赖倒置原则。

其英文定义如下:

High-level modules shouldn’t depend on low-level modules.

Both modules should depend on abstractions.

In addition, abstractions shouldn’t depend on details. Details depend on abstractions.

翻译过来大致如下:

高层模块不要依赖于低层模块。

高层与低层模块应该通过首相互相依赖。

另外,抽象不应该依赖于细节。细节应该依赖抽象。

这里面提到了“高层模块”、“低层模块”以及他们的“依赖关系”。

所谓高层模块和低层模块的划分,简单来说就是,在调用链上,调用者属于高层,被调用者属于低层。

在我们平常的代码开发活动中,比如使用MVC三层架构来开发的Web应用程序,controller就是作为高层模块调用service低层模块。在这种环境下,高层模块依赖于低层模块一点关系也没有。

反过来说,如果我们在日常开发工作中可以的对代码进行设计以满足依赖倒置原则,那务必会事倍功半,得不偿失了。

那依赖倒置设计原则的意义是什么呢???

依赖倒置原则的意义是什么

我们拿 Tomcat 这个 Servlet 容器作为例子来解释一下。我们编写的 Web 应用程序代码只需要部署在 Tomcat 容器下,便可以被 Tomcat 容器调用执行。Tomcat 就是高层模块,我们编写的 Web 应用程序代码就是低层模块。

如果Tomcat的设计是结构良好的且层次化清晰的,那它应该想下图这样:



考虑一下,这时高层模块依赖于低层模块意味着什么。Tomcat可能会因为你低层的逻辑变化倒置变化。这非常荒谬。Apache肯定是不会给你改的,需要你自己改。

所以这种情况下无论如何高层模块都不应该依赖于低层模块。根据依赖倒置原则调整后如下图所示:

按照依赖倒置原则重新进行调整。Tomcat 和应用程序代码之间并没有直接的依赖关系了,两者都依赖同一个“抽象”,也就是 Servlet 规范。Servlet 规范不依赖具体的 Tomcat 容器和应用程序的实现细节,而 Tomcat 容器和应用程序依赖 Servlet 规范。

上面说,依赖倒置原则并不适用于普通的业务开发。基于Tomcat这个例子来说,依赖倒置原则更加适合指导框架层面的设计。

如果你要去写一个框架,你肯定不希望底层模块的变动导致你框架的变动。如果真是这样,那也称不上是一个框架了。

所以用它来做顶层设计就是他最大的意义了吧!

那你可能会问,那除了在设计框架之外,就不能采用该原则了呗!也不是,只要你想用,你就可以用。毕竟气质这一块儿,还得自己拿捏。接下来举一个小例子。

一个生动的例子

我们设计一个用一个对象控制另一个对象的系统。

当Button接收到poll消息时,它会判断是否被“按下”。当然我们不关心用什么方式按下的,随意。

Lamp对象响应Button发来的消息。当收到turnOn时,它显示某种灯光。当接收到turnOff时,他熄灭灯光。我们也不管是什么灯了,随意。



我们做了第一版设计,比如下面类图所示:

现在按照调用链,Button依赖Lamp。上层依赖低层。如果在Lamp不变的情况下,足以满足需求。

如果此时Lamp发生变化。Button势必遭殃。Button控制着Lamp,它且仅能控制Lamp。对扩展关闭。违反了DIP原则。

可是设计是拥抱变化的呀!

我们按照上面Tomcat的抽象思路做一下设计调整。如下图所示:

现在我们倒置了Lamp对象的依赖关系。Button不在依赖具体实现,而是依赖ButtonServer这个抽象。

Lamp现在也是依赖这个抽象,不在被依赖了。

好了现在你想把Lamp换掉,随你了。

是不是瞬间感觉什么都无关紧要了呢!

总结

依赖反转原则也叫作依赖倒置原则。这条原则跟控制反转有点类似,主要用来指导框架层面的设计。高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。

附 为什么依赖倒置有时被叫做好莱坞原则

依赖倒置与好莱坞原则

好莱坞原则是这样描述的:别打电话给我们,有事我会打电话给你

所以好莱坞更像是我们使用的IoC,依赖注入容器。你不需要主动创建对象,如果你需要,我会主动给你。

而依赖倒置原则不同,他是指高层不依赖低层只依赖抽象,低层也依赖抽象。

二者还是有区别的。



用户头像

柳旭

关注

复杂的东西简单讲,简单的东西深刻讲。 2018.08.21 加入

已昏懒人

评论 (9 条评论)

发布
用户头像
赞赞赞
2020 年 06 月 16 日 10:36
回复
用户头像
感谢分享,InfoQ首页推荐。
2020 年 06 月 16 日 10:04
回复
感谢推荐
2020 年 06 月 16 日 13:08
回复
用户头像
顶顶
2020 年 06 月 16 日 09:49
回复
用户头像
配图很接地气,👍 !
2020 年 06 月 16 日 09:32
回复
用户头像
建议楼主练练字
2020 年 06 月 16 日 09:32
回复
用户头像
不错,推荐!
2020 年 06 月 16 日 09:32
回复
用户头像
加油,再次置顶!
2020 年 06 月 16 日 09:28
回复
用户头像
透彻
2020 年 06 月 16 日 09:28
回复
没有更多了
手撕设计原则:依赖倒置