北风设计模式课程---依赖倒置原则(Dependency Inversion Principle)

时间:2021-10-18 08:30:40

北风设计模式课程---依赖倒置原则(Dependency Inversion Principle)

一、总结

一句话总结:

面向对象技术的根基:依赖倒置原则(Dependency Inversion Principle)是很多面向对象技术的根基。它特别适合应用于构建可复用的软件框架,其对于构建弹性地易于变化的代码也特别重要。并且,因为抽象和细节已经彼此隔离,代码也变得更易维护。

1、软件设计中的"Bad Design" ?

1、影响多部分:难以修改,因为每次修改都影响系统中的多个部分。(僵化性Rigidity)
2、难以预料:当修改时,难以预期系统中哪些地方会被影响。(脆弱性Fragility)
3、难以重用:难以在其他应用中重用,因为它不能从当前系统中解耦。(复用性差Immobility)

2、那到底是什么让设计变得僵化、脆弱和难以复用呢?

模块间的相互依赖

3、依赖倒置原则实例?

copy功能依赖键盘输入和打字机输出-->copy依赖reader抽象层输入,writer抽象层输出,键盘输入依赖reader抽象层,打字机依赖于writer抽象层。

二、依赖倒置原则(Dependency Inversion Principle)

转自或参考:依赖倒置原则(Dependency Inversion Principle)
https://www.cnblogs.com/gaochundong/p/dependency_inversion_principle.html">依赖倒置原则(Dependency Inversion Principle)

 

很多软件工程师都多少在处理 "Bad Design"时有一些痛苦的经历。如果发现这些 "Bad Design" 的始作俑者就是我们自己时,那感觉就更糟糕了。那么,到底是什么让我做出一个能称为 "Bad Design" 的设计呢?

绝大多数软件工程师不会在设计之初就打算设计一个 "Bad Design"。许多软件也在不断地演化中逐渐地降级到了一个点,而从这个点开始,有人开始说这个设计已经腐烂到一定程度了。为什么会发生这些事情呢?是因为最初设计的匮乏吗,还是设计逐步降级到像块腐烂的肉一样?实际上,寻找这些答案得先从确定 "Bad Design" 的准确定义开始。

"Bad Design" 的定义

你可能曾经提出过一个让你倍感自豪的软件设计,然后让你的一个同事来做 Design Review?你能感觉到你同事脸上隐含的抱怨与嘲弄,他会冷笑的问道:"为什么你要这么设计?" 反正这事儿在我身上肯定是发生过,并且我也看到在我身边的很多工程师身上也发生过。确切的说,那些持不同想法的同事是没有采用与你相同的评判标准来断定 "Bad Design"。我见过最常使用的标准是 "TNNTWI-WHDI" ,也就是 "That's not the way I would have done it(要是我就不会这么干)" 标准。

但有一些标准是所有工程师都会赞同的。如果软件在满足客户需求的情况下,其呈现出了下述中的一个或多个特点,则就可称其为 "Bad Design":

  1. 难以修改,因为每次修改都影响系统中的多个部分。(僵化性Rigidity)
  2. 当修改时,难以预期系统中哪些地方会被影响。(脆弱性Fragility)
  3. 难以在其他应用中重用,因为它不能从当前系统中解耦。(复用性差Immobility)

此外,还有一些较难断定的 "Bad Design",比如:灵活性(Flexible)、鲁棒性(Robust)、可重用性(Reusable)等方面。我们可以仅使用上面明确的三点作为判定一个设计的好与坏的标准。

"Bad Design" 的根源

那到底是什么让设计变得僵化、脆弱和难以复用呢?答案是模块间的相互依赖

如果一个设计不能很容易被修改,则设计就是僵化的。这种僵化性体现在,如果对相互依赖严重的软件做一处改动,将会导致所有依赖的模块发生级联式的修改。当设计师或代码维护者无法预期这种级联式的修改所产生的影响时,那么这种蔓延的结果也就无法估计了。这导致软件变更的代价无法被准确的预测。而管理人员在面对这种无法预测的情况时,通常是不会对变更进行授权,然后僵化的设计也就得到了官方的保护。

脆弱性是指一处变更将破坏程序中多个位置的功能。而通常新产生的问题所涉及的模块与该变更所涉及的模块在概念上并没有直接的关联关系。这种脆弱性极大地削弱了设计与维护团队对软件的信任度。同时软件使用者和管理人员都不能预测产品的质量,因为对应用程序某一部分简单的修改导致了其他多个位置的错误,而且看起来还是完全无关的位置。而解决这些问题将可能导致更多的问题,使得维护过程陷进了 "狗咬尾巴" 的怪圈。

如果设计中实现需求的部分对一些与该需求无关的部分产生了很强的依赖,则该设计陷入了死板区域。设计师可能会被要求去调查是否能够将该设计应用到不同的应用程序,要能够预知该设计在新的应用中是否可以完好的工作。然而,如果设计的模块间是高度依赖的,而从一个功能模块中隔离另一个功能模块的工作量足以吓到设计师时,设计师就会放弃这种重用,因为隔离重用的代价已经高于重新设计的代价

示例:一个拷贝程序(Copy)

通过一个简单的例子来描述这些问题可能会对我们有所帮助。设想有一个简单的 "Copy" 程序,它负责将键盘上输入的字符拷贝到一个打印机上。假设设备独立而且是与平台无关的。我们可以构思这个程序的结构,类似于图 1 中的描述:

北风设计模式课程---依赖倒置原则(Dependency Inversion Principle)

图 1 拷贝程序

图 1 是一个结构图。它显示在应用程序中一共有三个模块,或者叫子程序。"Copy" 模块负责调用其他两个模块。可以简单的想象成在 "Copy" 中有一个 while 循环,在循环体内调用 "Read Keyboard" 模块来尝试从键盘读取一个字符,然后将字符发送到 "Write Printer" 模块来打印字符。

 void Copy()
{
int c;
while ((c = ReadKeyboard()) != EOF)
WritePrinter(c);
}

这个两层的模块设计是可以很好地被重用的,它们可以被使用到许多不同的应用程序中来控制对键盘和打印机的访问。

然而,"Copy" 模块在那些不使用键盘和打印机的条件下是无法被重用的。这太可惜了,因为系统所呈现的智能化就是体现在了这个模块里。"Copy" 模块封装了我们所感兴趣并且希望重用的部分。

例如,假设我们有一个新的程序,它需要将键盘字符拷贝到磁盘文件。我们显然希望复用 "Copy" 模块,因为它所做的高层封装正是我们需要的。而这个封装所做的就是描述将字符从源拷贝到目的地的过程。但很不幸,由于 "Copy" 模块直接依赖了 "Write Printer" 模块,所以这种新的需求情况下无法被重用。

当然,我们可以直接修改 "Copy" 模块来增加新的功能。通过增加 "if" 语句来检查一个标志位,判断到底是写到打印机还是写到磁盘,这样就可以分别使用 "Write Printer" 模块或 "Write Disk" 模块。然后,这样做之后我们就又在系统中增加了一个依赖模块。

 enum OutputDevice {printer, disk};
void Copy(outputDevice dev)
{
int c;
while ((c = ReadKeyboard()) != EOF)
if (dev == printer)
WritePrinter(c);
else
WriteDisk(c);
}

随时时间的推移,越来越多的设备可以支持 "Copy" 功能,"Copy" 模块也将陷入凌乱的 "if/else" 判断中。这显然使应用变得僵化和脆弱。

依赖倒置(Dependency Inversion)

上述问题的主要特征是包含高层逻辑的模块依赖于低层模块的细节,例如 "Copy" 模块依赖于 "Read Keyboard" 模块和 "Write Printer" 模块。如果我们想办法使 "Copy" 模块不依赖于这些细节,则就会很容易地被复用。可以将其用于任何其他负责从输入设备将字符拷贝到输出设备的应用程序。OOD 为我们提供了一种机制,叫做依赖倒置(Dependency Inversion)。

北风设计模式课程---依赖倒置原则(Dependency Inversion Principle)

图 2

设想如图 2 中的类图结构。类 "Copy" 包含了一个抽象类 "Reader" 和另一个抽象类 "Writer"。可以想象在 "Copy" 中的循环结构不断的从 "Reader" 读取字符,然后将字符发送至 "Writer"。

 class Reader
{
public:
virtual int Read() = ;
};
class Writer
{
public:
virtual void Write(char) = ;
};
void Copy(Reader& r, Writer& w)
{
int c;
while((c=r.Read()) != EOF)
w.Write(c);
}

此时类 "Copy" 既没有依赖 "Keyboard Reader" 也没有依赖 "Printer Writer"。因此,这些依赖已经被反转了(Inverted)。"Copy" 类依赖于抽象,而真正的 "Reader" 和 "Writer" 的具体实现也依赖于抽象。

此时,我们就可以重用 "Copy" 类,而不需要具体的 "Keyboard Reader" 和 "Printer Writer"。我们可以通过创造新的 "Reader" 和 "Writer" 衍生类然后替换到 "Copy" 中。而且,无论有多少种 "Reader" 和 "Writer" 被创建,"Copy" 都不会依赖于它们。因为没有这些模块间的相互依赖,也使得程序不会变的僵化和脆弱。并且 "Copy" 类也可以被复用到多种不同的情况中。它不再是固定的。

依赖倒置原则(The Dependency Inversion Principle)

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

B. Abstractions should not depend upon details. Details should depend upon abstraction.

A. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象。

B. 抽象不应该依赖于具体实现细节,而具体实现细节应该依赖于抽象。

北风设计模式课程---依赖倒置原则(Dependency Inversion Principle)

有人可能会问,为什么我要使用 "Inversion" 这个词儿。坦白的说,是因为,对于更加传统的软件开发方法,例如结构化的分析与设计(Structured Analysis and Design),更趋向于创建高层模块依赖于低层模块的软件结构,进而使得抽象依赖了具体实现细节。而且实际上这些方法最主要的目标就是通过定义子程序的层级关系来描述高层模块式如何调用低层模块的。图 1 中的示例正好描述了这样的一个层级结构。因此,一个设计良好的面向对象程序的依赖结构是 “inverted” 倒置了相对于传统过程化方法的依赖结构。

考虑下高层模块依赖于低层模块所带来的连带影响。高层模块包含着应用程序中重要的业务决策信息,是这些业务模型包含了应用程序的功能特征。当这些模块依赖于低层模块时,对低层模块的修改将直接影响高层模块,也就是强制修改了它们。

这种情形是违反常理的!应该是高层模块强制要求修改低层模块才对。高层模块的权重应该优先于低层模块。高层模块是无论如何都不应当依赖于低层模块。

更进一步说,我们其实想重用的是高层模块。我们已经通过子程序库等方式很好地重用了低层模块了。如果高层模块依赖于低层模块,将导致高层模块在不同的环境中变得极难被复用。而如果高层模块完全独立于与低层模块,高层模块就可以很容易地被复用。这就是这个原则的核心所在。

分层(Layering)

依据 Grady Booch 的定义:

All well-structured object-oriented architectures have clearly-defined layers, with each layer providing some coherent set of services though a well-defined and controlled interface.

所有结构良好的面向对象架构都有着清晰明确的层级定义,每一层都通过一个定义良好和可控的接口来提供一组内聚的服务集合。

如果不加思索的来解释这段话,可能会让设计师创建出类似于图3中的结构。

北风设计模式课程---依赖倒置原则(Dependency Inversion Principle)

图 3

在图中高层类 "Policy" 使用了低层类 "Mechanism","Mechanism" 使用了更细粒度的 "Utility" 类。这看起来像是很合适,但其实隐藏了一个问题,就是对于 Policy Layer 的更改将对一路下降至 Utility Layer。这称为依赖是传递的(Dependency is transitive)。Policy Layer 依赖一些依赖于 Utility Layer 的模块,然后 Policy Layer 传递性的依赖了 Utility Layer。这显示是非常不幸的。

图 4 给出了一个更合适的模型。

北风设计模式课程---依赖倒置原则(Dependency Inversion Principle)

图 4

每一个低层都被一个抽象类所表述,而实际的层级则由这些抽象类所派生。每一个高层类通过抽象接口来使用低层类。因此,层级之间不会依赖其他的层。取而代之的是,层依赖了抽象类。这不仅打破了 Policy Layer 到 Utility Layer 的传递性依赖,同时也将 Policy Layer 到 Mechanism Layer 的依赖打破。

使用这个模型后,Policy Layer 不会被任何 Mechanism Layer 或 Utility Layer 的更改所影响。同时,Policy Layer 也能够在任何情形下进行重用,只要是低层模块符合 Mechanism Layer Interface 定义即可。因此,通过反转依赖关系,沃恩稿件了一个更灵活、更持久的设计结构。

总结

依赖倒置原则(Dependency Inversion Principle)是很多面向对象技术的根基。它特别适合应用于构建可复用的软件框架,其对于构建弹性地易于变化的代码也特别重要。并且,因为抽象和细节已经彼此隔离,代码也变得更易维护。

面向对象设计的原则

参考资料

北风设计模式课程---依赖倒置原则(Dependency Inversion Principle)的更多相关文章

  1. 设计模式六大原则(三):依赖倒置原则(Dependence Inversion Principle)

    依赖倒置原则(DIP)定义: 高层模块不应该依赖低层模块,二者都应该依赖其抽象:抽象不应该依赖细节:细节应该依赖抽象. 问题由来: 类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码 ...

  2. 依赖倒置原则(Dependence Inversion Principle,DIP)

    依赖倒转原则就是 A.要依赖于抽象,不要依赖于实现.(Abstractions should not depend upon details. Details should depend upon a ...

  3. 北风设计模式课程---最少知识原则(Least Knowledge Principle)

    北风设计模式课程---最少知识原则(Least Knowledge Principle) 一.总结 一句话总结: 最少知识原则(Least Knowledge Principle),或者称迪米特法则( ...

  4. 北风设计模式课程---接口分离原则(Interface Segregation Principle)

    北风设计模式课程---接口分离原则(Interface Segregation Principle) 一.总结 一句话总结: 接口分离原则描述为 "客户类不应被强迫依赖那些它们不需要的接口& ...

  5. 北风设计模式课程---里氏替换原则(Liskov Substitution Principle)

    北风设计模式课程---里氏替换原则(Liskov Substitution Principle) 一.总结 一句话总结: 当衍生类能够完全替代它们的基类时:(Liskov Substitution P ...

  6. 北风设计模式课程---单一职责原则(Single Responsibility Principle)

    北风设计模式课程---单一职责原则(Single Responsibility Principle) 一.总结 一句话总结: 一个类应该有且只有一个变化的原因:单一职责原则(SRP:Single Re ...

  7. 北风设计模式课程---开放封闭原则(Open Closed Principle)

    北风设计模式课程---开放封闭原则(Open Closed Principle) 一.总结 一句话总结: 抽象是开放封闭原则的关键. 1."所有的成员变量都应该设置为私有(Private)& ...

  8. 依赖倒置(Dependence Inversion Principle)DIP

    关于抽象类和接口的区别,可以参考之前的文章~http://www.cnblogs.com/leestar54/p/4593173.html using System; using System.Col ...

  9. ASP.NET 设计模式中依赖倒置原则

    依赖倒置原则 A.高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象. B.抽象不应该依赖于具体,具体应该依赖于抽象. 依赖倒置原则 A.高层次的模块不应该依赖于低层次的模块,他们都应该依赖于 ...

随机推荐

  1. Neither the JAVA_HOME nor the JRE_HOME environment variable is defined

    执行远程shell,启动远程机器的tomat时遇到次错误.后来发现原来是远程机器的.profile被人改掉了! 在.profile里加入 export JAVA_HOME=/home/evans/jd ...

  2. C Socket Programming for Linux with a Server and Client Example Code

    Typically two processes communicate with each other on a single system through one of the following ...

  3. Entity Framework学习(一)

    网上看了很多的资料,发现都不是想要的学习资料,讲的不是很明白,最后在msdn开始自己研究EF MSDN的地址 https://msdn.microsoft.com/zh-cn/library/gg69 ...

  4. U3D 实现地面碰撞效果

    前面讲了如何让两个刚体碰撞: 现在来细细讲解一下, 首先,精灵刚体后就好比物理世界的物体,是受到重力所用的, 然后两个物体要添加碰撞系数才能实现碰撞, 这种情况下,碰撞后会使得另一个刚体也会随之运动, ...

  5. linux内核学习之进程管理------task_struct结构体

    struct task_struct { volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */ struct t ...

  6. [bzoj4821][Sdoi2017]相关分析

    来自FallDream的博客,未经允许,请勿转载,谢谢. Frank对天文学非常感兴趣,他经常用望远镜看星星,同时记录下它们的信息,比如亮度.颜色等等,进而估算出星星的距离,半径等等.Frank不仅喜 ...

  7. 放下技术,是PM迈出的第一步

    上一篇,我们从项目层面提出了PM的核心能力架构.今天,我想从公司层面,分析一下PM的核心能力架构中的过程能力,这也是PM当下最关心.最真切的痛点. 还记得上一篇我的同事老A吗? 为什么他能在知名外企带 ...

  8. D1 java概述

    首先扯点别的.在学习知识的过程中非常重要的一点是沟通交流,拿自学java来说绝不是抱着一本Head First Java闷头看.感觉自学入门这一阶段相当于启蒙,绝不能向无头苍蝇一样到处乱撞.java的 ...

  9. Luogu3527 POI2011 Meteors 整体二分、树状数组、差分

    传送门 比较板子的整体二分题目,时限有点紧注意常数 整体二分的过程中将时间在\([l,mid]\)之间的流星使用树状数组+差分进行维护,然后对所有国家查看一遍并分好类,递归下去,记得消除答案在\([m ...

  10. jquery作业 教授答案

    http://www.cnblogs.com/qianjinyan/p/8961086.html 题目要求: 1. 通过jquery动态的创建一个表格,随机生成(id自增,name随机2-3个中文汉字 ...