依赖倒转原则就是要依赖于抽象,不要依赖于实现。(Abstractions should not depend upon details. Details should depend upon abstractions.)要针对接口编程,不要针对实现编程。(Program to an interface, not an implementation.)也就是说应当使用接口和抽象类进行变量类型声明、参数类型声明、方法返还类型说明,以及数据类型的转换等。而不要用具体类进行变量的类型声明、参数类型声明、方法返还类型说明,以及数据类型的转换等。要保证做到这一点,一个具体类应当只实现接口和抽象类中声明过的方法,而不要给出多余的方法。
传统的过程性系统的设计办法倾向于使高层次的模块依赖于低层次的模块,抽象层次依赖于具体层次。倒转原则就是把这个错误的依赖关系倒转过来。面向对象设计的重要原则是创建抽象化,并且从抽象化导出具体化,具体化给出不同的实现。继承关系就是一种从抽象化到具体化的导出。抽象层包含的应该是应用系统的商务逻辑和宏观的、对整个系统来说重要的战略性决定,是必然性的体现。具体层次含有的是一些次要的与实现有关的算法和逻辑,以及战术性的决定,带有相当大的偶然性选择。具体层次的代码是经常变动的,不能避免出现错误。
从复用的角度来说,高层次的模块是应当复用的,而且是复用的重点,因为它含有一个应用系统最重要的宏观商务逻辑,是较为稳定的。而在传统的过程性设计中,复用则侧重于具体层次模块的复用。依赖倒转原则则是对传统的过程性设计方法的“倒转”,是高层次模块复用及其可维护性的有效规范。
特例:对象的创建过程是违背“开—闭”原则以及依赖倒转原则的,但通过工厂模式,能很好地解决对象创建过程中的依赖倒转问题。

2.关系

“开-闭”原则与依赖倒转原则是目标和手段的关系。如果说开闭原则是目标,依赖倒转原则是到达"开闭"原则的手段。如果要达到最好的"开闭"原则,就要尽量的遵守依赖倒转原则,依赖倒转原则是对"抽象化"的最好规范。

里氏代换原则是依赖倒转原则的基础,依赖倒转原则是里氏代换原则的重要补充。

3.耦合(依赖)关系的种类

零耦合(Nil Coupling)关系:两个类没有耦合关系
具体耦合(Concrete Coupling)关系:发生在两个具体的(可实例化的)类之间,经由一个类对另一个具体类的直接引用造成。
抽象耦合(Abstract Coupling)关系:发生在一个具体类和一个抽象类(或接口)之间,使两个必须发生关系的类之间存有最大的灵活性。

3.1如何把握耦合

我们应该尽可能的避免实现继承,原因如下:
1 失去灵活性,使用具体类会给底层的修改带来麻烦。
2 耦合问题,耦合是指两个实体相互依赖于对方的一个量度。程序员每天都在(有意识地或者无意识地)做出影响耦合的决定:类耦合、API耦合、应用程序耦合等等。在一个用扩展的继承实现系统中,派生类是非常紧密的与基类耦合,而且这种紧密的连接可能是被不期望的。如B extends A ,当B不全用A中的所有methods时,这时候,B调用的方法可能会产生错误!
我们必须客观的评价耦合度,系统之间不可能总是松耦合的,那样肯定什么也做不了。

3.2我们决定耦合程度的依据是什么

简单的说,就是根据需求的稳定性,来决定耦合的程度。对于稳定性高的需求,不容易发生变化的需求,我们完全可以把各类设计成紧耦合的(我们虽然讨论类之间的耦合度,但其实功能块、模块、包之间的耦合度也是一样的),因为这样可以提高效率,而且我们还可以使用一些更好的技术来提高效率或简化代码,例如c# 中的内部类技术。可是,如果需求极有可能变化,我们就需要充分的考虑类之间的耦合问题,我们可以想出各种各样的办法来降低耦合程度,但是归纳起来,不外乎增加抽象的层次来隔离不同的类,这个抽象层次可以是抽象的类、具体的类,也可以是接口,或是一组的类。我们可以用一句话来概括降低耦合度的思想:"针对接口编程,而不是针对实现编程。
在我们进行编码的时候,都会留下我们的指纹,如public的多少,代码的格式等等。我们可以耦合度量评估重新构建代码的风险。因为重新构建实际上是维护编码的一种形式,维护中遇到的那些麻烦事在重新构建时同样会遇到。我们知道在重新构建之后,最常见的随机bug大部分都是不当耦合造成的 。
如果不稳定因素越大,它的耦合度也就越大。
某类的不稳定因素=依赖的类个数/被依赖的类个数
依赖的类个数= 在编译此类的时被编译的其它类的个数总和

3.3怎样将大系统拆分成效系统

解决这个问题的一个思路是将许多类集合成一个更高层次的单位,形成一个高内聚、低耦合的类的集合,这是我们设计过程中应该着重考虑的问题!
耦合的目标是维护依赖的单向性,有时我们也会需要使用坏的耦合。在这种情况下,应当小心记录下原因,以帮助日后该代码的用户了解使用耦合真正的原因。

4.怎样做到依赖倒转

以抽象方式耦合是依赖倒转原则的关键。抽象耦合关系总要涉及具体类从抽象类继承,并且需要保证在任何引用到基类的地方都可以改换成其子类,因此,里氏代换原则是依赖倒转原则的基础。
在抽象层次上的耦合虽然有灵活性,但也带来了额外的复杂性,如果一个具体类发生变化的可能性非常小,那么抽象耦合能发挥的好处便十分有限,这时可以用具体耦合反而会更好。
层次化:所有结构良好的面向对象构架都具有清晰的层次定义,每个层次通过一个定义良好的、受控的接口向外提供一组内聚的服务。
依赖于抽象:建议不依赖于具体类,即程序中所有的依赖关系都应该终止于抽象类或者接口。尽量做到:
1、任何变量都不应该持有一个指向具体类的指针或者引用。
2、任何类都不应该从具体类派生。
3、任何方法都不应该覆写它的任何基类中的已经实现的方法。

5.依赖倒转原则的优缺点

依赖倒转原则虽然很强大,但却最不容易实现。因为依赖倒转的缘故,对象的创建很可能要使用对象工厂,以避免对具体类的直接引用,此原则的使用可能还会导致产生大量的类,对不熟悉面向对象技术的工程师来说,维护这样的系统需要较好地理解面向对象设计。

依赖倒转原则假定所有的具体类都是会变化的,这也不总是正确。有一些具体类可能是相当稳定,不会变化的,使用这个具体类实例的应用完全可以依赖于这个具体类型,而不必为此创建一个抽象类型。

DIP,Dependence Inversion Principle:

High level modules should not depend upon low level modules. Both should depend upon abstractions.

Abstractions should not depend upon details. Details should depend upon abstractions.

“面向接口编程”

  • 高层模块不应该依赖低层模块,两者都应该依赖其抽象;——模块间的依赖通过抽象发生。实现类之间不发生直接的依赖关系(eg. 类B被用作类A的方法中的参数),其依赖关系是通过接口或抽象类产生的;
  • 抽象不应该依赖细节;——接口或抽象类不依赖于实现类;
  • 细节应该依赖抽象;——实现类依赖接口或抽象类。

何为“倒置”?

依赖正置:类间的依赖是实实在在的实现类间的依赖,即面向实现编程,这是正常人的思维方式;

而依赖倒置是对现实世界进行抽象,产生了抽象间的依赖,代替了人们传统思维中的事物间的依赖。

依赖倒置可以减少类间的耦合性、降低并行开发引起的风险。

示例(减少类间的耦合性):

例如有一个Driver,可以驾驶Benz:

  1. public class Driver{
  2. public void drive(Benz benz){
  3. benz.run();
  4. }
  5. }
  6. public class Benz{
  7. public void run(){
  8. System.out.println("Benz开动...");
  9. }
  10. }

问题来了:现在有变更,Driver不仅要驾驶Benz,还需要驾驶BMW,怎么办?

Driver和Benz是紧耦合的,导致可维护性大大降低、稳定性大大降低(增加一个车就需要修改Driver,Driver是不稳定的)。

示例(降低并行开发风险性):

如上例,Benz类没开发完成前,Driver是不能编译的!不能并行开发!

问题由来:

类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

解决办法:

将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。

上例中,新增一个抽象ICar接口,ICar不依赖于BMW和Benz两个实现类(抽象不依赖于细节)。

1)Driver和ICar实现类松耦合

2)接口定下来,Driver和BMW就可独立开发了,并可独立地进行单元测试

依赖有三种写法:

      1)构造函数传递依赖对象(构造函数注入)

  1. public interface IDriver{
  2. public void drive();
  3. }
  4. public class Driver implements IDriver{
  5. private ICar car;
  6. public <strong>Driver(ICar _car)</strong>{
  7. this.car = _car;
  8. }
  9. public void drive(){
  10. this.car.run();
  11. }
  12. }

      2)setter方法传递依赖对象(setter依赖注入)

  1. public interface IDriver{
  2. public void setCar(ICar car);
  3. public void drive();
  4. }
  5. public class Driver implements IDriver{
  6. private ICar car;
  7. public void <strong>setCar(ICar car)</strong>{
  8. this.car = car;
  9. }
  10. public void drive(){
  11. this.car.run();
  12. }
  13. }

      3)接口声明依赖对象(接口注入)

建议:

DIP的核心是面向接口编程;DIP的本质是通过抽象(接口、抽象类)使各个类或模块的实现彼此独立,不互相影响。

在项目中遵循以下原则:

  1. 每个类尽量都有接口或抽象类
  2. 变量的表面类型尽量使接口或抽象类
  3. 任何类都不应该从具体类派生*——否则就会依赖具体类。
  4. 尽量不要重写父类中已实现的方法——否则父类就不是一个真正适合被继承的抽象。
  5. 结合里氏替代原则使用

软件设计原则(四)依赖倒置原则 -Dependence Inversion Principle相关推荐

  1. 软件设计原则之里氏替换原则、依赖倒置原则

    系列文章目录 软件设计原则之单一职责原则.开闭原则 软件设计原则之里氏替换原则.依赖倒置原则 软件设计原则之接口隔离原则.合成复用原则.迪米特原则 文章目录 系列文章目录 一.里氏替换原则 什么是里氏 ...

  2. 面向对象设计原则-03依赖倒置原则

    面向对象设计原则-03依赖倒置原则 依赖倒置原则的定义 依赖倒置原则(Dependence Inversion Principle,DIP)是 Object Mentor 公司总裁罗伯特·马丁(Rob ...

  3. 设计原则 单一职责原则、开放封闭原则、依赖倒置原则、里氏代换原则、迪米特法则

    目录 1 单一职责原则 2 开放封闭原则 3 依赖倒置原则 4 里氏代换原则 5 迪米特法则 1 单一职责原则 比如:电脑内存坏了就应该更换内存,不应该更换CPU (内存负责内存.CPU负责CPU) ...

  4. 7.12 其他面向对象设计原则3: 依赖倒置原则DIP

    其他面向对象设计原则3: 依赖倒置原则DIP  The Dependency Inversion Principle 7.1 依赖倒置原则DIP The Dependency Inversion P ...

  5. 设计模式六大原则之里氏替换原则、依赖倒置原则详解

    设计模式六大原则--里氏替换原则.依赖倒置原则详解 1.里氏代换原则(Liskov Substitution Principle) 概念 顾名思义,该原则用于经常发生替换的地方,在Java中指的是实现 ...

  6. 3.六大原则例子-- 依赖倒置原则(DIP)例子

    设计模式六大原则例子-- 依赖倒置原则(DIP)例子 之前我们对设计模式的六大原则做了简单归纳,这篇博客是对依赖倒置原则进行的举例说明. 依赖倒置原则的意义 DIP是6大原则中最难以实现的原则,它是实 ...

  7. 【软件架构设计原则】开闭原则和依赖倒置原则

    文章目录 软件架构设计原则 开闭原则 依赖倒置原则 最后 软件架构设计原则 本文通过实例来讲解 开闭原则 依赖导致原则 开闭原则 开闭原则(Open-Close Principle,OCP)是指一个软 ...

  8. 六大设计原则之依赖倒置原则07

    目录 1.概述 2.业务场景 3.运用设计原则前代码实现 3.1.代码实现 3.2.总结 4.运用设计原则后代码实现 4.1.代码实现 4.2.总结 1.概述 依赖倒置原则(Dependence In ...

  9. 软件架构设计原则-DIP依赖倒置原则

    一.什么是依赖倒转原则 依赖倒转(Dependence Inversion Principle ): 1.抽象不应该依赖于细节,细节应该依赖于抽象. 2.高层模块不依赖底层模块,两者都依赖抽象. 二. ...

  10. 面向对象七大设计原则之依赖倒置原则

    熟练掌握和应用面向对象设计七大原则,是初中级工程师向高级/资深工程师进阶的一个必备技能.他可以大大的提升程序的可复用性和可维护性是重构代码的一大利器. 本人菜鸟一枚本面文章的出发点是1.加深个人印象. ...

最新文章

  1. 微软自带输入法如何关闭桌面右下角「拼」图标
  2. mysql error 1231_解决ERROR 1231 (42000): Variable 'time_zone' can't
  3. Mac解决终端显示乱码
  4. 游戏界著名设计师 Cory Schmtiz:“灵感乍现”是设计生涯里的浪漫
  5. Jackson相关的一些注解
  6. php ip处理函数,PHP取ip地址函数
  7. javafx 图标_JavaFX技巧32:需要图标吗? 使用Ikonli!
  8. python网络编程知识点_python 网络编程要点
  9. 超简单的mysql多实例布置
  10. 【IEEE出版】计算机多主题征稿,ICBASE 2020诚邀您投稿参会!
  11. Android RecyclerView使用详解(一)
  12. linux双系统uefi引导修复,Windows和Ubuntu双系统,修复UEFI引导的两种办法
  13. js:数据结构笔记10--图和图算法
  14. python气象卫星云图解析_【我教你系列】想要实时的地球图像作为桌面?
  15. aspectj tomcat load-time waver
  16. 计算机怎么学要记笔记,留法十全大补汤 | 学姐告诉你在法国上课如何记笔记,复习,考试!...
  17. python图片转视频加特效_视频剪切成图像+图像合成视频+python
  18. centos5安装nagios
  19. 数字IC开发软件介绍
  20. 3698: XWW的难题 有源汇上下界最大流

热门文章

  1. 10行代码AC——7-2 说反话-加强版 (20 分)——解题报告
  2. php 接口说明文档,phpwind文章中心接口说明
  3. exfat linux 读写速度,Ubuntu / Xubuntu : 读写 exFAT 文件系统
  4. 全程图解交换机和路由器的应用
  5. Java连接SQL2005及SQL Server JDBC Driver 2.0中sqljdbc.jar和sqljdbc4.jar的区别
  6. jq遍历子元素_leetcode第196周赛第三题统计全 1 子矩形
  7. mysql安装1335_Mysql 安装问题。提示MySQL Server 5.1 -- Error 1335.
  8. python 多继承 __new___Python3中的__new__方法以及继承不可变类型类的问题
  9. oracle命令窗口粘贴,Oracle数据库中的Copy命令
  10. java return none,返回列表结果为none