《設計模式之美》- 設計原則

1.SOLID 原則:SRP 單一職責原則

一個類只負責完成一個職責或者功能。單一職責原則通過避免設計大而全的類,避免將不相關的功能耦合在一起,來提高類的內聚性。同時,類職責單一,類依賴的和被依賴的其他類也會變少,減少了代碼的耦合性,以此來實現代碼的高內聚、松耦合。但是,如果拆分得過細,實際上會適得其反,反倒會降低內聚性,也會影響代碼的可維護性。

不同的應用場景、不同階段的需求背景、不同的業務層面,對同一個類的職責是否單一,可能會有不同的判定結果。實際上,一些側面的判斷指標更具有指導意義和可執行性,比如,出現下面這些情況就有可能說明這類的設計不滿足單一職責原則:

  • 類中的代碼行數、函數或者屬性過多;
  • 類依賴的其他類過多或者依賴類的其他類過多;
  • 私有方法過多;比較難給類起一個合適的名字;
  • 類中大量的方法都是集中操作類中的某幾個屬性。

2.SOLID 原則:OCP 開閉原則

如何理解“對擴展開放、修改關閉”?

添加一個新的功能,應該是通過在已有代碼基礎上擴展代碼(新增模塊、類、方法、屬性等),而非修改已有代碼(修改模塊、類、方法、屬性等)的方式來完成。關於定義,我們有兩點要注意。第一點是,開閉原則並不是說完全杜絕修改,而是以最小的修改代碼的代價來完成新功能的開發。第二點是,同樣的代碼改動,在粗代碼粒度下,可能被認定爲“修改”;在細代碼粒度下,可能又被認定爲“擴展”。

如何做到“對擴展開放、修改關閉”?

我們要時刻具備擴展意識、抽象意識、封裝意識。在寫代碼的時候,我們要多花點時間思考一下,這段代碼未來可能有哪些需求變更,如何設計代碼結構,事先留好擴展點,以便在未來需求變更的時候,在不改動代碼整體結構、做到最小代碼改動的情況下,將新的代碼靈活地插入到擴展點上。

很多設計原則、設計思想、設計模式,都是以提高代碼的擴展性爲最終目的的。特別是 23 種經典設計模式,大部分都是爲了解決代碼的擴展性問題而總結出來的,都是以開閉原則爲指導原則的。最常用來提高代碼擴展性的方法有:多態、依賴注入、基於接口而非實現編程,以及大部分的設計模式(比如,裝飾、策略、模板、職責鏈、狀態)。

3.SOLID 原則:LSP 裏式替換原則

子類對象(object of subtype/derived class)能夠替換程序(program)中父類對象(object of base/parent class)出現的任何地方,並且保證原來程序的邏輯行爲(behavior)不變及正確性不被破壞。

裏式替換原則是用來指導繼承關係中子類該如何設計的一個原則。理解裏式替換原則,最核心的就是理解“design by contract,按照協議來設計”這幾個字。父類定義了函數的“約定”(或者叫協議),那子類可以改變函數的內部實現邏輯,但不能改變函數的原有“約定”。這裏的“約定”包括:函數聲明要實現的功能;對輸入、輸出、異常的約定;甚至包括註釋中所羅列的任何特殊說明。

理解這個原則,我們還要弄明白,裏式替換原則跟多態的區別。雖然從定義描述和代碼實現上來看,多態和裏式替換有點類似,但它們關注的角度是不一樣的。多態是面向對象編程的一大特性,也是面向對象編程語言的一種語法。它是一種代碼實現的思路。而裏式替換是一種設計原則,用來指導繼承關係中子類該如何設計,子類的設計要保證在替換父類的時候,不改變原有程序的邏輯及不破壞原有程序的正確性。

4.SOLID 原則:ISP 接口隔離原則

接口隔離原則的描述是:客戶端不應該強迫依賴它不需要的接口。其中的“客戶端”,可以理解爲接口的調用者或者使用者。理解“接口隔離原則”的重點是理解其中的“接口”二字。這裏有三種不同的理解。

接口隔離原則的描述是:客戶端不應該強迫依賴它不需要的接口。其中的“客戶端”,可以理解爲接口的調用者或者使用者。理解“接口隔離原則”的重點是理解其中的“接口”二字。這裏有三種不同的理解。

如果把“接口”理解爲單個 API 接口或函數,部分調用者只需要函數中的部分功能,那我們就需要把函數拆分成粒度更細的多個函數,讓調用者只依賴它需要的那個細粒度函數。

如果把“接口”理解爲 OOP 中的接口,也可以理解爲面向對象編程語言中的接口語法。那接口的設計要儘量單一,不要讓接口的實現類和調用者,依賴不需要的接口函數。

單一職責原則針對的是模塊、類、接口的設計。接口隔離原則相對於單一職責原則,一方面更側重於接口的設計,另一方面它的思考的角度也是不同的。接口隔離原則提供了一種判斷接口的職責是否單一的標準:通過調用者如何使用接口來間接地判定。如果調用者只使用部分接口或接口的部分功能,那接口的設計就不夠職責單一。

5.SOLID 原則:DIP 依賴倒置原則

控制反轉:實際上,控制反轉是一個比較籠統的設計思想,並不是一種具體的實現方法,一般用來指導框架層面的設計。這裏所說的“控制”指的是對程序執行流程的控制,而“反轉”指的是在沒有使用框架之前,程序員自己控制整個程序的執行。在使用框架之後,整個程序的執行流程通過框架來控制。流程的控制權從程序員“反轉”給了框架。

依賴注入:依賴注入和控制反轉恰恰相反,它是一種具體的編碼技巧。我們不通過 new 的方式在類內部創建依賴類的對象,而是將依賴的類對象在外部創建好之後,通過構造函數、函數參數等方式傳遞(或“注入”)給類來使用。

依賴注入框架:我們通過依賴注入框架提供的擴展點,簡單配置一下所有需要的類及其類與類之間的依賴關係,就可以實現由框架來自動創建對象、管理對象的生命週期、依賴注入等原本需要程序員來做的事情。

依賴反轉原則:依賴反轉原則也叫作依賴倒置原則。這條原則跟控制反轉有點類似,主要用來指導框架層面的設計。高層模塊不依賴低層模塊,它們共同依賴同一個抽象。抽象不需要依賴具體實現細節,具體實現細節依賴抽象。

6.KISS、YAGNI 原則

KISS 原則的中文描述是:儘量保持簡單。KISS 原則是保持代碼可讀和可維護的重要手段。KISS 原則中的“簡單“”並不是以代碼行數來考量的。代碼行數越少並不代表代碼越簡單,我們還要考慮邏輯複雜度、實現難度、代碼的可讀性等。而且,本身就複雜的問題,用複雜的方法解決,也並不違背 KISS 原則。除此之外,同樣的代碼,在某個業務場景下滿足 KISS 原則,換一個應用場景可能就不滿足了。

對於如何寫出滿足 KISS 原則的代碼,我總結了下面幾條指導原則:

  • 不要使用同事可能不懂的技術來實現代碼;
  • 不要重複造輪子,善於使用已經有的工具類庫;
  • 不要過度優化。

YAGNI 原則的英文全稱是:You Ain’t Gonna Need It。直譯就是:你不會需要它。這條原則也算是萬金油了。當用在軟件開發中的時候,它的意思是:不要去設計當前用不到的功能;不要去編寫當前用不到的代碼。實際上,這條原則的核心思想就是:不要做過度設計。

YAGNI 原則跟 KISS 原則並非一回事兒。KISS 原則講的是“如何做”的問題(儘量保持簡單),而 YAGNI 原則說的是“要不要做”的問題(當前不需要的就不要做)。

7.DRY 原則

DRY 原則中文描述是:不要重複自己,將它應用在編程中,可以理解爲:不要寫重複的代碼。

專欄中講到了三種代碼重複的情況:實現邏輯重複、功能語義重複、代碼執行重複。實現邏輯重複,但功能語義不重複的代碼,並不違反 DRY 原則。實現邏輯不重複,但功能語義重複的代碼,也算是違反 DRY 原則。而代碼執行重複也算是違反 DRY 原則。

除此之外,我們還講到了提高代碼複用性的一些手段,包括:減少代碼耦合、滿足單一職責原則、模塊化、業務與非業務邏輯分離、通用代碼下沉、繼承、多態、抽象、封裝、應用模板等設計模式。複用意識也非常重要。在設計每個模塊、類、函數的時候,要像設計一個外部 API 一樣去思考它的複用性。

我們在第一次寫代碼的時候,如果當下沒有複用的需求,而未來的複用需求也不是特別明確,並且開發可複用代碼的成本比較高,那我們就不需要考慮代碼的複用性。在之後開發新的功能的時候,發現可以複用之前寫的這段代碼,那我們就重構這段代碼,讓其變得更加可複用。

相比於代碼的可複用性,DRY 原則適用性更強些。我們可以不寫可複用的代碼,但一定不能寫重複的代碼。

8.LOD 原則

如何理解“高內聚、松耦合”?

“高內聚、松耦合”是一個非常重要的設計思想,能夠有效提高代碼的可讀性和可維護性,縮小功能改動導致的代碼改動範圍。“高內聚”用來指導類本身的設計,“松耦合”用來指導類與類之間依賴關係的設計。所謂高內聚,就是指相近的功能應該放到同一個類中,不相近的功能不要放到同一類中。相近的功能往往會被同時修改,放到同一個類中,修改會比較集中。所謂“松耦合”指的是,在代碼中,類與類之間的依賴關係簡單清晰。即使兩個類有依賴關係,一個類的代碼改動也不會或者很少導致依賴類的代碼改動。

如何理解“迪米特法則”?
迪米特法則的描述爲:不該有直接依賴關係的類之間,不要有依賴;有依賴關係的類之間,儘量只依賴必要的接口。迪米特法則是希望減少類之間的耦合,讓類越獨立越好。每個類都應該少了解系統的其他部分。一旦發生變化,需要了解這一變化的類就會比較少。

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