SOLID設計原則--依賴倒置原則
- 背景
- SOLID設計原則
- 依賴倒置原則(DIP)
-
- 什麼是依賴倒置原則
-
- 定義
- 解釋說明
- 依賴倒置原則的使用
-
- 需求描述
- 解決方案一
-
- 優缺點分析
- 解決方案二
- 誰和誰的依賴被倒置了?
- 總結
背景
- 設計原則–>設計模式–>程式語言文法機制,是程式設計思考和實施的三個層次。由左向右抽象層次越來越低,工作内容越來越具體。文法機制提供了機制和實施的可能性,設計模式是如何操作這些機制,設計模式可以看做是設計原則的具現化,設計模式遵循了設計原則,提供針對重複問題的最佳解決方案。設計原則指導設計模式的産生。
- 設計原則是心法,設計模式是招式,程式語言文法機制是基本能力(能動、能跳、能推)。
- 違背程式語言文法機制的代碼段是不能正确工作的,不違背程式語言文法機制是作為程式員的
;違背設計原則而寫出的程式,會使程式的品質屬性[可擴充,可維護,可測試,可重用,…]急劇下降。了解設計原則,并會使用設計模式的程式員是成為基本格
的必要條件。中進階工程師
- 設計原則影響的範圍最廣,影響效果最長久,也最潛移默化。不經曆一個較長時間的軟體生長和演化過程,設計原則的影響很難展現出來。
- 比設計原則更抽象的思維層次是設計思想,比如
設計思想、面向過程、面向對象、函數式程式設計思想。高内聚低耦合
SOLID設計原則
- 經過面向對象程式設計領域幾十年的發展和總結,誕生了五個重要的設計原則(單一職責原則、開閉原則、裡氏替換原則、接口隔離原則和依賴倒置原則),後來這幾個原則被Bob大叔調整了下順序,其首字母剛好是單詞
,是以後來就被被稱為solid
原則,SOLID
原則起初主要是在OOP中針對接口設計的指導原則,其實這些原則還可以作為函數、類以及子產品的設計原則。遵循這幾個原則可以編寫出擴充性好,可維護性好,透明性好,可讀性好的軟體系統。SOLID
- 總的來說
設計原則還是程式設計方面的具體原則。在軟體工程中針對的SOLID
或level
大概是處于架構設計和編碼實作的中間階段 ------- 程式詳細設計階段。phase
依賴倒置原則(DIP)
什麼是依賴倒置原則
定義
- 高層子產品不應該依賴于低層子產品,二者都應該依賴于抽象;
- 抽象不能依賴于實作細節,實作細節應該依賴于抽象。
解釋說明
- 依賴倒置原則(DIP)雖然是位于
最後一個,但其實,DIP是非常基礎和重要的設計原則。其餘四個原則幾乎都或多或少有DIP的影子,比如違反了DIP,也大機率會違反開閉原則(OCP)。SOLID
依賴倒置原則的使用
我們通過對一個具體需求的分析、設計和實作來說明如何遵循依賴倒置原則。違背和不違背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
聚合(Aggregation)到類Lamp
。類Switch
是高層子產品,類Swith
是低層子產品。依賴關系為類Lamp
到Switch
,任何導緻Lamp
變化的原因都會導緻Lamp
的變動,這種依賴關系是靜态的,編譯時的依賴。大部分情況下高層子產品Switch
的代碼相對穩定,隻具有Swith
和open
兩種操作,而這兩種操作都是轉調用底層子產品的具體實作close
。而低層子產品{open(); close()}
相對不穩定,目前的依賴關系違背了依賴導緻原則,高層子產品Lamp
直接依賴的低層子產品,導緻高層Switch
不易變化的子產品的代碼和低層Switch
容易變化的代碼靜态綁定到一起,繼而導緻高層子產品Lamp
的代碼不可複用。為了解決Switch
可複用性問題,我們引入解決方案二。Switch
解決方案二
- 為了複用
代碼,我們将引入一個抽象的中間類(引入一個中間層[分層]是計算機領域解決問題的好方法),名稱為Switch
來抽象IControlable
可以操作的所有對象,包括Switch
對象,甚至未來的Lamp
、Motor
等所有具有二進制[0-1]操作的可控制對象。Vehicle
// 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
了,這就是DIP原則的兩種定義所描述的完整内涵----高層子產品不應該依賴低層子產品,二者都應該依賴于抽象;抽象不應該依賴實作細節,實作細節應該依賴于抽象。這裡Lamp
是高層子產品,Switch
是低層子產品也是實作細節,而Lamp
是抽象。至此,該設計完全遵循DIP原則了。IControlable
- 對于C++語言來說,此時在編譯時,類
檔案Switch
也隻依賴switch.hpp
,不會有IControlable.hpp
之Lamp
的影子。lamp.hpp
誰和誰的依賴被倒置了?
- 一般來說控制器
的能力由Switch
來描述和限制,我們會把IControlable
也看做高層子產品的一部分,即IControlable
屬于高層子產品。類圖如下右邊所示:IControlable
- 依賴關系由之前的 高層
子產品直接依賴低層和實作細節子產品Switch
, 進化為 低層Lamp
子產品依賴了高層Lamp
子產品,由之前的穩定依賴于不穩定進化為不穩定依賴于穩定。這就是依賴倒置中倒置的深刻含義。Switch
總結
- 今天分析了
原則 ----依賴倒置原則(SOLID
)的含義,并從複用性和可擴充性方面給出了一個例子,希望讀者能認真思考,舉一反三,将設計原則轉化為自己的程式設計内功,修煉和提高自己的程式設計素養。DIP