設計模式,及軟件設計中的“套路”。每一個模式描述了一個在我們周圍不斷重復發生的問題,以及該問題解決方案的核心,這樣,你就能一次又一次的使用該方案而不必做重復的勞動。設計模式大約有20多種,它們使人們可以更加簡單方便的復用成功的設計和體系結構,提高系統維護的有效性。與設計模式密切相關的是6大設計原則,那么就從這些設計原則開始設計模式重溫之旅吧。(ps:內容有點小多)
一、6大設計模式
1、單一職責原則
- 核心思想:應該有且僅有一個原因引起類的變更
- 問題描述:假如有類Class1完成職責T1,T2,當職責T1或T2有變更需要修改時,有可能影響到該類的另外一個職責正常工作。
- 好處:類的復雜度降低、可讀性提高、可維護性提高、擴展性提高、降低了變更引起的風險。
- 需注意: 單一職責原則提出了一個編寫程序的標准,用“職責”或“變化原因”來衡量接口或類設計得是否優良,但是“職責”和“變化原因”都是不可以度量的,因項目和環境而異。
單一職責就是讓類(或方法)的功能單一,做到術業有且專攻,是不是有點UNIX設計思想的感覺?
2、里斯替換原則
- 核心思想:在使用基類的的地方可以任意使用其子類,能保證子類完美替換基類。
- 通俗來講:只要父類能出現的地方子類就能出現。反之,父類則未必能勝任。
- 好處:增強程序的健壯性,即使增加了子類,原有的子類還可以繼續運行。
- 需注意:如果子類不能完整地實現父類的方法,或者父類的某些方法在子類中已經發生“畸變”,則建議斷開父子繼承關系 采用依賴、聚合、組合等關系代替繼承。
3、依賴倒置原則
- 核心思想:高層模塊不應該依賴底層模塊,二者都該依賴其抽象;抽象不應該依賴細節;細節應該依賴抽象;
- 說明:高層模塊就是調用端,低層模塊就是具體實現類。抽象就是指接口或抽象類。細節就是實現類。
- 通俗來講:依賴倒置原則的本質就是通過抽象(接口或抽象類)使個各類或模塊的實現彼此獨立,互不影響,實現模塊間的松耦合。
- 問題描述:類A直接依賴類B,假如要將類A改為依賴類C,則必須通過修改類A的代碼來達成。這種場景下,類A一般是高層模塊,負責復雜的業務邏輯;類B和類C是低層模塊,負責基本的原子操作;假如修改類A,會給程序帶來不必要的風險。
- 解決方案:將類A修改為依賴接口interface,類B和類C各自實現接口interface,類A通過接口interface間接與類B或者類C發生聯系,則會大大降低修改類A的幾率。
- 好處:依賴倒置的好處在小型項目中很難體現出來。但在大中型項目中可以減少需求變化引起的工作量。使並行開發更友好。
4、接口隔離原則
- 核心思想:類間的依賴關系應該建立在最小的接口上
- 通俗來講:建立單一接口,不要建立龐大臃腫的接口,盡量細化接口,接口中的方法盡量少。也就是說,我們要為各個類建立專用的接口,而不要試圖去建立一個很龐大的接口供所有依賴它的類去調用。
- 問題描述:類A通過接口interface依賴類B,類C通過接口interface依賴類D,如果接口interface對於類A和類B來說不是最小接口,則類B和類D必須去實現他們不需要的方法。
- 需注意:
-
- 接口盡量小,但是要有限度。對接口進行細化可以提高程序設計靈活性,但是如果過小,則會造成接口數量過多,使設計復雜化。所以一定要適度
- 提高內聚,減少對外交互。使接口用最少的方法去完成最多的事情
- 為依賴接口的類定制服務。只暴露給調用的類它需要的方法,它不需要的方法則隱藏起來。只有專注地為一個模塊提供定制服務,才能建立最小的依賴關系。
5、迪米特原則
- 核心思想:類間解耦。
- 通俗來講: 一個類對自己依賴的類知道的越少越好。自從我們接觸編程開始,就知道了軟件編程的總的原則:低耦合,高內聚。無論是面向過程編程還是面向對象編程,只有使各個模塊之間的耦合盡量的低,才能提高代碼的復用率。低耦合的優點不言而喻,但是怎么樣編程才能做到低耦合呢?那正是迪米特法則要去完成的。
6、開閉原則
- 核心思想:盡量通過擴展軟件實體來解決需求變化,而不是通過修改已有的代碼來完成變化
- 通俗來講: 一個軟件產品在生命周期內,都會發生變化,既然變化是一個既定的事實,我們就應該在設計的時候盡量適應這些變化,以提高項目的穩定性和靈活性。
設計原則概括
單一職責原則告訴我們實現類要職責單一;里氏替換原則告訴我們不要破壞繼承體系;依賴倒置原則告訴我們要面向接口編程;接口隔離原則告訴我們在設計接口的時候要精簡單一;迪米特法則告訴我們要降低耦合。而開閉原則是總綱,他告訴我們要對擴展開放,對修改關閉。
二、23種設計模式
------------------------------創建型模式------------------------------
1、單例模式
單例模式確保一個類只有一個實例,自行初始化並向整個系統提供這個實例。一個類只有一個實例減少了內存開支,特別是一個對象頻繁創建銷毀時。單例模式可細分為2種模式,餓漢式和懶漢式,二者的區別是初始化實例的時間不同,前者是在定義時就初始化,后者是在第一次使用時初始化,這里注意多線程的並發訪問。
2、工廠方法模式
工廠方法(Factory Method)模式的意義是定義一個創建產品對象的工廠接口,將實際創建工作推遲到子類當中。核心工廠類不再負責產品的創建,這樣核心類成為一個抽象工廠角色,僅負責具體工廠子類必須實現的接口,這樣進一步抽象化的好處是使得工廠方法模式可以使系統在不修改具體工廠角色的情況下引進新的產品。

3、抽象工廠模式
為創建一組相關或相互依賴的對象提供一個接口,而且無須指定它們的具體類,可以理解為工廠方法的升級版。

4、建造者模式
建造者模式也叫作生成器模式,將一個復雜對象的構建和它的表示分離,使得同樣的構建過程可以創建不同的表示。比如一個User類的屬性有name、age、address、email、job...等,如果想創建一個User對象,傳入全部的屬性有點太長了,這時可以使用建造者模式,傳入一個參數就只設置對應屬性的值。

5、原型模式
原型模式的定義為:用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。原型模式的核心是一個clone()方法,通過該方法進行對象的復制,Java提供了一個Cloneable來標示這個對象是可拷貝的,為什么說是“標示”呢?這個接口只是一個標記作用,在JVM中具有這個標記的對象才有可能被拷貝。那怎么才能從“有可能被拷貝”到“可以被拷貝”呢?方式就是覆蓋clone()方法。

------------------------------結構型模式------------------------------
6、適配器模式
適配器模式定義:將一個類的接口變換為客戶端所期待的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作。
適配器模式的注意事項
適配器模式最好在詳細設計階段不要考慮它, 它不是為了解決還處在開發階段的問題,而是解決正在服役的項目問題,沒有一個系統分析師會在做詳細設計的時候考慮使用適配器模式,這個模式使用的主要場景是擴展應用中,就像我們上面的那個例子一樣,系統擴展了,不符合原有設計的時候才考慮通過適配器模式減少代碼修改帶來的風險。
7、裝飾模式
動態地給一個對象添加額外的職責,就增加功能來說,裝飾模式相比於生成子類更加靈活。裝飾模式相當於在一個構建類外層添加了一個包裹類。

8、代理模式
代理模式是一種使用率非常高的模式,為其他對象提供一種代理以控制對這個對象的訪問。

在Java中有一種動態代理技術,動態代理指實現階段不用關心代理誰,而在運行階段才指定代理哪一個對象。
9、組合模式
組合模式將對象組合成樹形結構以表示“整體-部分”的層次結構,使得用戶對單個對象和組合對象的使用具有一致性。

- Component抽象構件角色:定義參加組合對象的共有方法和屬性, 可以定義一些默認的行為或屬性, 比如我們例子中的getInfo就封裝到了抽象類中。
- Leaf葉子構件:葉子對象, 其下再也沒有其他的分支, 也就是遍歷的最小單位。
- Composite樹枝構件:樹枝對象, 它的作用是組合樹枝節點和葉子節點形成一個樹形結構。
10、門面模式
門面模式要求一個子系統的外部與其內部的通信必須通過一個統一的對象進行。門面模式提供一個高層次的接口,使得子系統更易於使用。


- Facade門面角色:客戶端可以調用這個角色的方法。 此角色知曉子系統的所有功能和責任。 一般情況下,本角色會將所有從客戶端發來的請求委派到相應的子系統去, 也就說該角色沒有實際的業務邏輯, 只是一個委托類。
- subsystem子系統角色:可以同時有一個或者多個子系統。 每一個子系統都不是一個單獨的類, 而是一個類的集合。 子系統並不知道門面的存在。 對於子系統而言, 門面僅僅是另外一個客戶端而已。
11、享元模式
享元模式是池技術的重要實現,使用共享對象可有效地支持大量的細粒度的對象。

12、橋接模式
橋梁模式定義:將抽象和實現解耦,使得兩者可以獨立的變化。

- Abstraction——抽象化角色:它的主要職責是定義出該角色的行為, 同時保存一個對實現化角色的引用, 該角色一般是抽象類。
- Implementor——實現化角色:它是接口或者抽象類, 定義角色必需的行為和屬性。
- RefinedAbstraction——修正抽象化角色:它引用實現化角色對抽象化角色進行修正。
- ConcreteImplementor——具體實現化角色:它實現接口或抽象類定義的方法和屬性。
------------------------------行為模式------------------------------
13、責任鏈模式
責任鏈模式定義:使多個對象都有機會處理請求,從而避免了請求的發送者和接收者之間的耦合關系。將這些對象組成一條鏈,並沿着這條鏈來傳遞請求,直到有對象處理它為止。

14、迭代器模式
迭代器模式提供了一種方法訪問一個容器對象中各個元素,而又不暴露該對象的內部細節(目前已很少用到了,容器一般都提供了迭代器)。

15、中介者模式
用一個中介對象封裝一系列的對象交互,中介者使各對象不需要顯示地互相作用,從而使其耦合松散,而且可以獨立地改變它們之間的交互。

終結者模式的優點:減少類間的依賴,把原來的一對多的依賴變成了一對一的依賴,同事類只依賴中介者,較少了依賴,降低了類間的耦合。
終結者模式的缺點:終結者會膨脹的很大,而且邏輯很復雜,原來N個對象直接的相互依賴關系轉換為中介者和同事類的依賴關系,同事類越多,中介者的邏輯越復雜。
16、模板方法
定義一個操作中算法的框架,而將一些步驟延遲到子類中。使得子類可以不改變一個算法即可重定義該算法的某些特定步驟。

基本方法:也叫作基本操作,是由子類實現的方法,並且在模板方法被調用
模板方法:可以有一個或幾個, 一般是一個具體方法, 也就是一個框架, 實現對基本方法的調度,完成固定的邏輯。一般將模板方法加上final關鍵字,不允許被覆寫
17、狀態模式
狀態模式定義:當一個對象內在狀態改變時允許其改變行為,這個對象看起來像改變了其類。狀態模式的核心是封裝,狀態的變更引起了行為的變更,從外部看起來就好像這個對象對應的類發生了改變一樣。

● State——抽象狀態角色
接口或抽象類, 負責對象狀態定義, 並且封裝環境角色以實現狀態切換。
● ConcreteState——具體狀態角色
每一個具體狀態必須完成兩個職責:本狀態的行為管理以及趨向狀態處理,通俗地說,就是本狀態下要做的事情, 以及本狀態如何過渡到其他狀態。
● Context——環境角色
定義客戶端需要的接口, 並且負責具體狀態的切換。
18、策略模式
策略模式是比較簡單的模式,定義一組算法,將每個算法封裝起來,並且使它們之間可以互換。

19、備忘錄模式
備忘錄模式定義:在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態。這樣以后就可將該對象恢復到原先保存的狀態。

20、觀察者模式
觀察者模式(Observer Pattern)也叫作發布訂閱模式,其定義如下:定義對象間一種一對多的依賴關系,使得每當一個對象改變狀態,則所有依賴於它的對象都會得到通知並自動更新。

21、訪問者模式
封裝一些作用於某種數據結構中的個元素的操作,它可以在不改變數據結構的前提下定義作用於這些元素的新的操作。

22、命令模式
命令模式是一個高內聚的模式,其定義為:將一個請求封裝成一個對象,從而讓你使用功能不同的請求把客戶端參數化,對請求排列或記錄請求日志,可以提供命令的撤銷和恢復功能。

23、解釋器模式
解釋器模式定義:給定一門語言, 定義它的文法的一種表示, 並定義一個解釋器, 該解釋器使用該表示來解釋語言中的句子。
參考:
1、《設計模式之禪》
2、《設計模式-可復用面對對象軟件的基礎》
3、https://github.com/luoxn28/ThinkInTechnology/tree/master/DesignPattern
