天天看點

SOLID設計原則--依賴倒置原則背景SOLID設計原則依賴倒置原則(DIP)總結

SOLID設計原則--依賴倒置原則

  • 背景
  • SOLID設計原則
  • 依賴倒置原則(DIP)
    • 什麼是依賴倒置原則
      • 定義
      • 解釋說明
    • 依賴倒置原則的使用
      • 需求描述
      • 解決方案一
        • 優缺點分析
      • 解決方案二
    • 誰和誰的依賴被倒置了?
  • 總結

背景

  • 設計原則–>設計模式–>程式語言文法機制,是程式設計思考和實施的三個層次。由左向右抽象層次越來越低,工作内容越來越具體。文法機制提供了機制和實施的可能性,設計模式是如何操作這些機制,設計模式可以看做是設計原則的具現化,設計模式遵循了設計原則,提供針對重複問題的最佳解決方案。設計原則指導設計模式的産生。
  • 設計原則是心法,設計模式是招式,程式語言文法機制是基本能力(能動、能跳、能推)。
  • 違背程式語言文法機制的代碼段是不能正确工作的,不違背程式語言文法機制是作為程式員的

    基本格

    ;違背設計原則而寫出的程式,會使程式的品質屬性[可擴充,可維護,可測試,可重用,…]急劇下降。了解設計原則,并會使用設計模式的程式員是成為

    中進階工程師

    的必要條件。
  • 設計原則影響的範圍最廣,影響效果最長久,也最潛移默化。不經曆一個較長時間的軟體生長和演化過程,設計原則的影響很難展現出來。
  • 比設計原則更抽象的思維層次是設計思想,比如

    高内聚低耦合

    設計思想、面向過程、面向對象、函數式程式設計思想。

SOLID設計原則

  • 經過面向對象程式設計領域幾十年的發展和總結,誕生了五個重要的設計原則(單一職責原則、開閉原則、裡氏替換原則、接口隔離原則和依賴倒置原則),後來這幾個原則被Bob大叔調整了下順序,其首字母剛好是單詞

    solid

    ,是以後來就被被稱為

    SOLID

    原則,

    SOLID

    原則起初主要是在OOP中針對接口設計的指導原則,其實這些原則還可以作為函數、類以及子產品的設計原則。遵循這幾個原則可以編寫出擴充性好,可維護性好,透明性好,可讀性好的軟體系統。
  • 總的來說

    SOLID

    設計原則還是程式設計方面的具體原則。在軟體工程中針對的

    level

    phase

    大概是處于架構設計和編碼實作的中間階段 ------- 程式詳細設計階段。

依賴倒置原則(DIP)

什麼是依賴倒置原則

定義

  • 高層子產品不應該依賴于低層子產品,二者都應該依賴于抽象;
  • 抽象不能依賴于實作細節,實作細節應該依賴于抽象。

解釋說明

  • 依賴倒置原則(DIP)雖然是位于

    SOLID

    最後一個,但其實,DIP是非常基礎和重要的設計原則。其餘四個原則幾乎都或多或少有DIP的影子,比如違反了DIP,也大機率會違反開閉原則(OCP)。

依賴倒置原則的使用

我們通過對一個具體需求的分析、設計和實作來說明如何遵循依賴倒置原則。違背和不違背DIP原則分别對擴充性有什麼影響。比如有一個業務需求:

需求描述

我期待通過一個二值自鎖開關類來控制一個兩狀态的台燈。按下開關,台燈打開;擡起開關,台燈關閉。

解決方案一

  • 這裡我們通過最直接的做法來滿足需求,作為做原始的疊代版本,保留程式的演化和成長過程。
  • 通過一個二值自鎖開關類來控制一個兩狀态的台燈,我們對這個需求經過簡單的面向對象分析後,把關鍵名詞挑出來作為類名,把動詞作為方法命。于是有了

    class Switch

    class 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 Switch

    class 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 <lamp.hpp>

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_;
};

           

優缺點分析

  • 優點是簡單直接,沒有思維負擔,也滿足了功能。我們畫下目前的類圖來看下目前依賴關系。
  • 這裡我們重點關注類

    Switch

    和類

    Lamp

    。類

    Lamp

    聚合(Aggregation)到類

    Switch

    。類

    Swith

    是高層子產品,類

    Lamp

    是低層子產品。依賴關系為類

    Switch

    Lamp

    ,任何導緻

    Lamp

    變化的原因都會導緻

    Switch

    的變動,這種依賴關系是靜态的,編譯時的依賴。大部分情況下高層子產品

    Swith

    的代碼相對穩定,隻具有

    open

    close

    兩種操作,而這兩種操作都是轉調用底層子產品的具體實作

    {open(); close()}

    。而低層子產品

    Lamp

    相對不穩定,目前的依賴關系違背了依賴導緻原則,高層子產品

    Switch

    直接依賴的低層子產品,導緻高層

    Switch

    不易變化的子產品的代碼和低層

    Lamp

    容易變化的代碼靜态綁定到一起,繼而導緻高層子產品

    Switch

    的代碼不可複用。為了解決

    Switch

    可複用性問題,我們引入解決方案二。

解決方案二

  • 為了複用

    Switch

    代碼,我們将引入一個抽象的中間類(引入一個中間層[分層]是計算機領域解決問題的好方法),名稱為

    IControlable

    來抽象

    Switch

    可以操作的所有對象,包括

    Lamp

    對象,甚至未來的

    Motor

    Vehicle

    等所有具有二進制[0-1]操作的可控制對象。
// file icontrolable.hpp

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

public:
  virtual bool open() = 0;
  virtual bool close() = 0;
};
           
  • 我們讓

    Switch

    依賴接口類

    IControlable

    ,讓類

    Lamp

    實作接口類

    IControlable

    ,解耦

    Switch

    Lamp

    的直接依賴關系,解除

    Switch

    對象隻能控制

    Lamp

    對象的尴尬局面(😃-😃-😃)。此時類圖如下
  • 現在的依賴關系變成了

    Switch

    依賴接口類

    IControlable

    ;同時

    Lamp

    也依賴接口類

    IControlable

    Switch

    不在依賴

    Lamp

    了,這就是DIP原則的兩種定義所描述的完整内涵----高層子產品不應該依賴低層子產品,二者都應該依賴于抽象;抽象不應該依賴實作細節,實作細節應該依賴于抽象。這裡

    Switch

    是高層子產品,

    Lamp

    是低層子產品也是實作細節,而

    IControlable

    是抽象。至此,該設計完全遵循DIP原則了。
  • 對于C++語言來說,此時在編譯時,類

    Switch

    檔案

    switch.hpp

    也隻依賴

    IControlable.hpp

    ,不會有

    Lamp

    lamp.hpp

    的影子。

誰和誰的依賴被倒置了?

  • 一般來說控制器

    Switch

    的能力由

    IControlable

    來描述和限制,我們會把

    IControlable

    也看做高層子產品的一部分,即

    IControlable

    屬于高層子產品。類圖如下右邊所示:
    SOLID設計原則--依賴倒置原則背景SOLID設計原則依賴倒置原則(DIP)總結
  • 依賴關系由之前的 高層

    Switch

    子產品直接依賴低層和實作細節子產品

    Lamp

    , 進化為 低層

    Lamp

    子產品依賴了高層

    Switch

    子產品,由之前的穩定依賴于不穩定進化為不穩定依賴于穩定。這就是依賴倒置中倒置的深刻含義。

總結

  • 今天分析了

    SOLID

    原則 ----依賴倒置原則(

    DIP

    )的含義,并從複用性和可擴充性方面給出了一個例子,希望讀者能認真思考,舉一反三,将設計原則轉化為自己的程式設計内功,修煉和提高自己的程式設計素養。

繼續閱讀