SOLID设计原则--依赖倒置原则

SOLID设计原则--依赖倒置原则

  • 背景
  • SOLID设计原则
  • 依赖倒置原则(DIP)
    • 什么是依赖倒置原则
      • 定义
      • 解释说明
    • 依赖倒置原则的使用
      • 需求描述
      • 解决方案一
        • 优缺点分析
      • 解决方案二
    • 谁和谁的依赖被倒置了?
  • 总结

背景

  • 设计原则–>设计模式–>程序语言语法机制,是编程思考和实施的三个层次。由左向右抽象层次越来越低,工作内容越来越具体。语法机制提供了机制和实施的可能性,设计模式是如何操作这些机制,设计模式可以看做是设计原则的具现化,设计模式遵循了设计原则,提供针对重复问题的最佳解决方案。设计原则指导设计模式的产生。
  • 设计原则是心法,设计模式是招式,程序语言语法机制是基本能力(能动、能跳、能推)。
  • 违背程序语言语法机制的代码段是不能正确工作的,不违背程序语言语法机制是作为程序员的基本格;违背设计原则而写出的程序,会使程序的质量属性[可扩展,可维护,可测试,可重用,…]急剧下降。理解设计原则,并会使用设计模式的程序员是成为中高级工程师的必要条件。
  • 设计原则影响的范围最广,影响效果最长久,也最潜移默化。不经历一个较长时间的软件生长和演化过程,设计原则的影响很难体现出来。
  • 比设计原则更抽象的思维层次是设计思想,比如高内聚低耦合设计思想、面向过程、面向对象、函数式编程思想。

SOLID设计原则

  • 经过面向对象程序设计领域几十年的发展和总结,诞生了五个重要的设计原则(单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则),后来这几个原则被Bob大叔调整了下顺序,其首字母刚好是单词solid,因此后来就被被称为SOLID原则,SOLID原则起初主要是在OOP中针对接口设计的指导原则,其实这些原则还可以作为函数、类以及模块的设计原则。遵循这几个原则可以编写出扩展性好,可维护性好,透明性好,可读性好的软件系统。
  • 总的来说SOLID设计原则还是程序设计方面的具体原则。在软件工程中针对的levelphase大概是处于架构设计和编码实现的中间阶段 ------- 程序详细设计阶段。

依赖倒置原则(DIP)

什么是依赖倒置原则

定义

  • 高层模块不应该依赖于低层模块,二者都应该依赖于抽象;
  • 抽象不能依赖于实现细节,实现细节应该依赖于抽象。

解释说明

  • 依赖倒置原则(DIP)虽然是位于SOLID最后一个,但其实,DIP是非常基础和重要的设计原则。其余四个原则几乎都或多或少有DIP的影子,比如违反了DIP,也大概率会违反开闭原则(OCP)。

依赖倒置原则的使用

我们通过对一个具体需求的分析、设计和实现来说明如何遵循依赖倒置原则。违背和不违背DIP原则分别对扩展性有什么影响。比如有一个业务需求:

需求描述

我期待通过一个二值自锁开关类来控制一个两状态的台灯。按下开关,台灯打开;抬起开关,台灯关闭。

解决方案一

  • 这里我们通过最直接的做法来满足需求,作为做原始的迭代版本,保留程序的演化和成长过程。
  • 通过一个二值自锁开关类来控制一个两状态的台灯,我们对这个需求经过简单的面向对象分析后,把关键名词挑出来作为类名,把动词作为方法命。于是有了class Switchclass Lamp两个类。
    我们先定义客户端的使用程序:
#include "lamp.hpp"
#include "switch.hpp"

// wrapper 类,组装两个功能类对象
class Client {
public:
  Client() {
    // 创建一个位于卧室的台灯对象
    lamp_ = Lamp("bed room");
    // 将创建到好的台灯对象交给Swich对象来管理,继而实现控制
    switch_ = Switch(lamp_); // 注意这里用的是引用或指针而不是值拷贝
  }

  // 开灯
  bool openLamp() { return switch_.open(); }

  // 关灯
  bool closeLamp() { return switch_.close(); }

private:
  Lamp lamp_;
  Switch switch_;
};

// Application main entry point
void main() {
  Client client;
  client.openLamp();
  client.closeLamp();
}
  • 下面给出class Switchclass Lamp两个类实现。
  • Lamp
// file lamp.hpp
class Lamp {
public:
  Lamp(const std::string &location) : location_(location) {}

public:
  bool open() {
    bool flag = false;
    // 具体实现细节
    // ...
    return flag;
  }
  bool close() {
    bool flag = false;
    // 具体实现细节
    // ...
    return flag;
  }

private:
  std::string location_;
};
  • Switch
// file switch.
#include 

class Switch {
public:
  // 构造域
  Switch(Lamp *lamp) : lamp_(lamp) {}

  Switch(Lamp &lamp) : lamp_(&lamp) {}

  // 接口域
public:
  bool open() { return lamp_->open(); }
  bool close() { return lamp_->close(); }

private:
  Lamp *lamp_;
};

优缺点分析

  • 优点是简单直接,没有思维负担,也满足了功能。我们画下当前的类图来看下当前依赖关系。
Client lamp_ switch_ openLamp() closeLamp() Switch lamp_ : Lamp* open() close() Lamp open() close()
  • 这里我们重点关注类Switch和类Lamp。类Lamp聚合(Aggregation)到类Switch。类Swith是高层模块,类Lamp是低层模块。依赖关系为类SwitchLamp,任何导致Lamp变化的原因都会导致Switch的变动,这种依赖关系是静态的,编译时的依赖。大部分情况下高层模块Swith的代码相对稳定,只具有openclose两种操作,而这两种操作都是转调用底层模块的具体实现{open(); close()}。而低层模块Lamp相对不稳定,目前的依赖关系违背了依赖导致原则,高层模块Switch直接依赖的低层模块,导致高层Switch不易变化的模块的代码和低层Lamp容易变化的代码静态绑定到一起,继而导致高层模块Switch的代码不可复用。为了解决Switch可复用性问题,我们引入解决方案二

解决方案二

  • 为了复用Switch代码,我们将引入一个抽象的中间类(引入一个中间层[分层]是计算机领域解决问题的好方法),名称为IControlable来抽象Switch可以操作的所有对象,包括Lamp对象,甚至未来的MotorVehicle等所有具有二元[0-1]操作的可控制对象。
// file icontrolable.hpp

// 抽象类 或 接口类
class IControlable {
public:
  virtual ~IControlable() {}

public:
  virtual bool open() = 0;
  virtual bool close() = 0;
};
  • 我们让Switch依赖接口类IControlable,让类Lamp实现接口类IControlable,解耦SwitchLamp的直接依赖关系,解除Switch对象只能控制Lamp对象的尴尬局面(--)。此时类图如下
Client lamp_ switch_ openLamp() closeLamp() Switch lamp_ : Lamp* open() close() «interface» IControlable +open() +close() Lamp open() close()
  • 现在的依赖关系变成了Switch依赖接口类IControlable;同时Lamp也依赖接口类IControlableSwitch不在依赖Lamp了,这就是DIP原则的两种定义所描述的完整内涵----高层模块不应该依赖低层模块,二者都应该依赖于抽象;抽象不应该依赖实现细节,实现细节应该依赖于抽象。这里Switch是高层模块,Lamp是低层模块也是实现细节,而IControlable是抽象。至此,该设计完全遵循DIP原则了。
  • 对于C++语言来说,此时在编译时,类Switch文件switch.hpp也只依赖IControlable.hpp,不会有Lamplamp.hpp的影子。

谁和谁的依赖被倒置了?

  • 一般来说控制器Switch的能力由IControlable来描述和限制,我们会把IControlable也看做高层模块的一部分,即IControlable属于高层模块。类图如下右边所示:
    SOLID设计原则--依赖倒置原则_第1张图片

  • 依赖关系由之前的 高层Switch模块直接依赖低层和实现细节模块Lamp, 进化为 低层Lamp模块依赖了高层Switch模块,由之前的稳定依赖于不稳定进化为不稳定依赖于稳定。这就是依赖倒置中倒置的深刻含义。

总结

  • 今天分析了 SOLID原则 ----依赖倒置原则(DIP)的含义,并从复用性和可扩展性方面给出了一个例子,希望读者能认真思考,举一反三,将设计原则转化为自己的编程内功,修炼和提高自己的编程素养。

你可能感兴趣的:(设计原则,设计模式)