軟件構造 SOLID設計原則

[軟件構造] 06 SOLID設計原則

軟件構造課程已經進行到了第5章,慢慢地進入到了深水區,涉及到了各種各樣的面向不同質量目標的設計模式和OO的基本設計原則。對於沒寫過太多代碼的我來說,想要將它們熟練的運用起來,不僅需要理解課程的講義,還要閱讀一些前人已經總結好的書籍與文章,更重要的是編寫更多的代碼。因而本文將我在學習SOLID設計原則過程中閱讀的一些文章和代碼總結起來,在對學過的內容的不斷總結的過程中不斷地提高自己。

1. 簡介

SOLID設計原則是由羅伯特·C·馬丁在21世紀早期引入面向對象編程和麪向對象設計中的五個基本原則的首字母縮寫,它們分別是:

  • (SRP)Single-responsibility principle 單一職責原則
  • (OCP)Open–closed principle 開閉原則
  • (LSP)Liskov substitution principle Liskov替換原則
  • (ISP)Interface Segregation Principle 接口隔離原則
  • (DIP)Dependency inversion principle 依賴倒置原則

2. 單一職責原則

首先,單一職責原則中的職責一詞的解釋爲引起變化的原因,這一原則描述如下:

There should never be more than one reason for a class to change.

引起類變化的因素永遠不要多於一個。

這和模塊化設計中的“高內聚,低耦合”中的高內聚是相似的,也就是說讓一個類只做它應該做的事情,某種程度上說是在分離關注點,將一個類能做的多個關聯度不大的兩件事情(兩個職責)分離開來。

public interface IPhone{
  //撥通電話
  public void dial(String phoneNumber);

  //通話
  public void chat(Object o);

  //掛斷電話
  public void hangup();
}

分析上面這個接口,卻發現它包含了兩個職責:一個是連接管理,一個是數據傳送。IPhone接口包含了兩個職責,而且這兩個職責的變化不互相影響,這就可以考慮分成兩個接口

public interface Connection{
  //撥通電話
  public void dial(String phoneNumber);

  //掛斷電話
  public void hangup();
}

public interface DataChannel{
  //通話
  public void chat(Object o);
}

單一職責原則是最簡單的原則,卻也是最難做好的原則。什麼時候要拆分,什麼時候要合併?這需要你不斷“品嚐”你的代碼,當“味道”不夠好時,對代碼持續重構,直到“味道”剛剛好。

3. 開閉原則

software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.

軟件實體(類、模塊等)應當對擴展開放,對修改閉合。

實現開閉原則的關鍵是抽象。我們要將系統中可能變化的地方封裝起來,即對修改封閉,可以通過接口、抽象類、策略模式等方法來實現開閉原則。

設計模式中的strategy模式是這個原則的一個很好的體現。

通過構造一個抽象的 Server 類:AbstractServer,該抽象類中包含針對所有類型的 Server 都通用的代碼,從而實現了對修改的封閉
當出現新的 Server 類型時,只需從該抽象類中派生出具體的子類 ConcreteServer 即可,從而支持了對擴展的開放

4. Liskov替換原則

之前已經對Liskov替換原則進行過詳細的總結,在此不再贅述。

5. 接口隔離原則

no client should be forced to depend on methods it does not use.

客戶端不應該被迫依賴於它不使用的方法。

也就是說更簡潔和更具體的瘦接口比龐大臃腫、職責過多的胖接口好。這是因爲胖的接口的職責過多,不夠聚合,因而很容易違反單一職責原則。

而怎樣將胖接口變成瘦接口呢?這就要依賴與客戶端了!接口如果變得過瘦,就不能夠想客戶端提供它所需要的服務。因而應該面向不同的客戶端,對接口進行適當的減肥,從而使得客戶端只訪問自己所需要的接口和對於的服務。

接口隔離原則本質上也是單一職責原則的體現,同時它也服務於里氏替換原則。

6. 依賴倒置原則

A. High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces).
B. Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

高層模塊不應該依賴底層模塊,兩者都應該依賴其抽象。
抽象不應該依賴於實現細節,而實現細節應該依賴於抽象。

這個原則其實是在指導如何實現接口隔離原則。

上圖的關係中,當Button直接調用燈的開和關時,Button就依賴於燈了。也就是說上層的clent直接調用了下層具體實現的類

public class Button {   
    private Lamp lamp;  

    public void Poll()   {
        if (/*some condition*/)
           lamp.TurnOn();   
    } 
}

而如果Button還想控制電視機,微波爐怎麼辦?
因而上層的client的代碼應該面向抽象的接口進行編程,從而隔離上層穩定的部分和下層變化的部分,避免對下層具體實現的類的直接調用。

不管是電燈,還是電視機,只要實現了ButtonServer,Button都可以控制。這是面向對象的編程方式。

7. 五大原則之間的關聯

只有當這些原則被一起應用時,它們才能使一個程序員開發一個容易進行維護和擴展的系統變得更加可能。因而必須理解它們之間的關係,並綜合應用這五大原則。只有把SOLID作爲一個整體,纔可能構建出堅實(Solid)的軟件。

  • 單一職責原則是所有設計原則的基礎,開閉原則是設計的終極目標。
  • 里氏替換原則強調的是子類替換父類後程序運行時的正確性,它用來幫助實現開閉原則。
  • 接口隔離原則用來幫助實現里氏替換原則,同時它也體現了單一職責原則。
  • 依賴倒置原則是過程式編程與OO編程的分水嶺,同時它也被用來指導接口隔離原則。

8. 寫在最後

課程學了這麼多的設計模式和設計原則,總結起來大概就是:
將變化的部分和不變的部分隔離開來,不斷地進行解耦合,從而便於以後可能的擴展與複用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章