目錄
1. 工廠方法(Factory Method)
2. 抽象工廠(Abstract Factory)
3. 原型模式(Prototype)
4. 構建器(Builder)
5. 門面模式(Facade)
6. 代理模式(Proxy)
7. 適配器(Adapter)
8. 中介者(Mediator)
「對象創建」模式
通過「對象創建」模式繞開 new,來避免對象創建(new)過程中所導致的緊耦合(依賴具體類),從而支持對象創建的穩定。它是接口抽象之后的第一步工作。
-
典型模式
- Factory Method(工廠方法)
- Abstract Factory(抽象工廠)
- Prototype(原型模式)
- Builder(構建器)
1. 工廠方法(Factory Method)
定義一個用于創建對象的接口,讓子類決定實例化哪一個類。Factory Method 使得一個類的實例化延遲(目的:解耦,手段:虛函數)到子類。
——《設計模式》GoF
動機
在軟件系統中,經常會面臨著創建對象的工作;由于需求的變化,需要創建的對象的具體類型經常變化。
如何應對這種變化?如何繞過常規的對象創建方法(new),提供一種「封裝機制」來避免客戶程序和這種「具體對象創建工作」的緊耦合?
結構
筆記
- 工廠方法模式實現時,客戶端需要決定實例化哪一個工廠來實現運算類,選擇判斷的問題還是存在的,也就是說,工廠方法把簡單工廠的內部邏輯判斷轉移到了客戶端代碼來運行。你想要加功能,本來是改工廠類的,而現在是修改客戶端。
要點總結
Factory Method 模式用于隔離類對象的使用者和具體類型之間的耦合關系。面對一個經常變化的具體類型,緊耦合關系(new)會導致軟件的脆弱。
Factory Method 模式通過面向對象的手法,將所要創建的對象工作延遲到子類,從而實現一種擴展(而非更改)的策略,較好地解決了這種緊耦合關系。
Factory Method 模式解決「單個對象」的需求變化。缺點在于要求創建方法/參數相同。
2. 抽象工廠(Abstract Factory)
提供一個接口,讓該接口負責創建一系列「相關或相互依賴的對象」,無需指定他們具體的類。
——《設計模式》GoF
動機
在軟件系統中,經常會面臨著「一系列相互依賴的對象」的創建工作;同時,由于需求的變化,往往存在更多系列對象的創建工作。
如何應對這種變化?如何繞過常規的對象創建方法(new),提供一種「封裝機制」來避免客戶程序和這種「多系列具體對象創建工作」的緊耦合?
結構
筆記
抽象工廠的最大的好處是易于交換產品系列,由于具體工廠類,在一個應用中只需要在初始化的時候出現一次,這就使得改變一個應用的具體工廠變得非常容易,它只需要改變具體工廠即可使用不同的產品配置。
第二大好處是它讓具體的創建實例過程與客戶端分離,客戶端是通過它們的抽象接口操縱實例,產品的具體類名也被具體工廠的實現分離,不會出現在客戶代碼上。
要點總結
如果沒有對應“多系列對象構建”的需求變化,則沒有必要使用 Abstract Factory 模式,這時候使用簡單的工廠完全可以。
“系列對象”指的是在某一特定系列下的對象之間有相互依賴或作用的關系。不同系列的對象之間不能相互依賴。
Abstract Factory 模式主要在于應對“新系列”的需求變動,其缺點在于難以應對“新對象”的需求變動。
3. 原型模式(Prototype)
使用原型實例指定創建對象的種類,然后通過拷貝(深拷貝)這些原型來創建新的對象。
——《設計模式》GoF
動機
在軟件系統中,經常面臨著“某些結構復雜的對象”(簡單的 new 達不到需要的效果)的創建工作;由于需求的變化,這些對象經常面臨著劇烈的變化,但是他們卻擁有著比較穩定一致的接口。
如何應對這種變化?如何向「客戶程序(使用這些對象的程序)」隔離出「這些易變對象」,從而使得「依賴這些易變對象的客戶程序」不隨著需求改變而改變?
結構
筆記
原型模式其實就是從一個對象再創建另外一個可定制的對象,而且不需知道任何創建的細節。
一般在初始化的信息不發生變化的情況下,克隆是最好的辦法。這既隱藏了對象創建的細節,又對性能是大大的提高。
原型模式等于是不用重新初始化對象,而是動態地獲得對象運行時的狀態。
問:什么時候使用原型模式?什么時候使用工廠方法?
答:當創建對象是非常簡單的幾個步驟時,用工廠方法;當創建對象時有非常復雜的中間狀態,且希望保留這個中間狀態時,用原型模式。
要點總結
Prototype 模式同樣用于隔離類對象的使用者和具體類型(易變類)之間的耦合關系,他同樣要求這些“易變類”擁有“穩定的接口”。
Prototype 模式對于“如何創建易變類的實體對象”采用“原型克隆”的方法來做,它使得我們可以非常靈活地動態創建「擁有某些穩定接口」的新對象——所需工作僅僅是注冊一個新類的對象(即原型),然后在任何需要的地方 Clone。
Prototype 模式中的 Clone 方法可以利用某些框架中的序列化來實現深拷貝。(C++ 中使用拷貝構造函數就能很好的實現)
4. 構建器(Builder)
將一個復雜對象的構建與其表示相分離,使得同樣的構建過程(穩定)可以創建不同的表示(變化)。
——《設計模式》GoF
動機
在軟件系統中,有時候面臨著“一個復雜對象”的創建工作,其通常由各個部分的子對象用一定的算法構成;由于需求的變化,這個復雜對象的各個部分經常面臨著劇烈的變化,但是將他們組合在一起的算法卻相對穩定。
如何應對這種變化?如何提供一種「封裝機制」來隔離出「復雜對象的各個部分」的變化,從而保持系統中的「穩定構建算法」不隨著需求改變而改變?
結構
筆記
在 C++ 中,在構造函數里面,調用虛函數是調用父類的虛函數而不是子類的虛函數(靜態綁定)。
使用構建器模式,用戶只需指定需要建造的類型就可以得到它們,而具體建造的過程和細節就不需知道了。
構建器模式的好處就是使得建造代碼與表示代碼分離,由于建造者隱藏了該產品是如何組裝的,所以若需要改變一個產品的內部表示,只需要再定義一個具體的建造者就可以了。
構建器模式是在當創建復雜對象的算法應該獨立于該對象的組成部分以及它們的裝配方式時適用的模式。
要點總結
Builder 模式主要用于“分步驟構建一個復雜對象”。在這其中“分步驟”是一個穩定的算法,而復雜對象的各個部分則經常變化。
變化點在哪里,封裝哪里——Builder 模式主要在于應對“復雜對象各個部分”的頻繁需求變動。其缺點在于難以應對“分步驟構建算法”的需求變動。
在 Builder 模式中,要注意不同語言中構造器內調用虛函數的差別(C++ vs. C#)。(靜態綁定 vs. 動態綁定)
「接口隔離」模式
在組件構建過程中,某些接口之間直接的依賴常常會帶來很多問題,甚至根本無法實現。采用添加一層間接(穩定)接口,來隔離本來相互緊密關聯的接口是一種常見的解決方案。
-
典型模式
- Facade(門面模式)
- Proxy(代理模式)
- Adapter(適配器)
- Mediator(中介者)
5. 門面模式(Facade)
為子系統中的一組接口提供一個一致(穩定)的界面,Facade 模式定義了一個高層接口,這個接口使得這一子系統更加容易使用(復用)。
——《設計模式》GoF
結構
動機
上述方案 A 的問題在于組件的客戶和組件中的各種復雜的子系統有了過多的耦合,隨著外部客戶程序和各子系統的演化,這種過多的耦合面臨很多變化的挑戰。
如何簡化外部客戶程序和系統間的交互接口?如何將外部客戶程序的演化和內部子系統的變化之間的依賴相互解耦?
筆記
首先,在設計初期階段,應該要有意識的將不同的兩個層分離,層與層之間建立 Facade。
其次,在開發階段,子系統往往因為不斷的重構演化而變得越來越復雜,增加
Facade 可以提供一個簡單的接口,減少它們之間的依賴。第三,在維護一個遺留的大型系統時,可能這個系統已經非常難以維護和擴展了。為新系統開發一個 Facade 類,來提供設計粗糙或高度復雜的遺留代碼的比較清晰簡單的接口,讓新系統與 Facade 對象交互(Facade 與遺留代碼交互所有復雜的工作)。
要點總結
從客戶程序的角度來看,Facade 模式簡化了整個組件系統的接口,對于組件內部與外部客戶程序來說,達到了一種“解耦”的效果——內部子系統的任何變化不會影響到 Facade 接口的變化。
Facade 設計模式更注重架構的層次去看整個系統,而不是單個類的層次。Facade 很多時候更是一種架構設計模式。
Facade 設計模式并非一個集裝箱,可以任意地放進任何多個對象。Facade 模式中組件的內部應該是“相互耦合關系比較大的一系列組件”,而不是簡單的功能集合,以便能夠實現松耦合,高內聚的特性。
6. 代理模式(Proxy)
動機
在面向對象系統中,有些對象由于某種原因(比如對象創建開銷很大,或者某些操作需要安全控制,或者需要進程外的訪問等),直接訪問會給使用者或者系統結構帶來很多麻煩。
如何在不失去透明操作對象的同時來管理/控制這些對象特有的復雜性?增加一層間接層是軟件開發中常見的解決方式。
為其他對象提供一種代理以控制(隔離,使用接口)對這個對象的訪問。
——《設計模式》GoF
結構
筆記
遠程代理:也就是為一個對象在不同的地址空間提供局部代表。這樣可以隱藏一個對象存在于不同地址空間的事實。
虛擬代理:根據需要創建開銷很大的對象。通過它來存放實例化需要很長時間的真實對象。
安全代理:用來控制真實對象訪問時的權限。
智能指引:當調用真實的對象時,代理處理另外一些事。
要點總結
“增加一層間接層”是軟件系統中對許多復雜問題的一種常見解決方案。在面向對象系統中,直接使用某些對象會帶來很多問題,作為間接層的 Proxy 對象便是解決這一問題的常用手段。
具體 Proxy 設計模式的實現方法、實現粒度都相差很大,有些可能對單個對象做細粒度的控制,如 copy-on-write 技術,有些可能對組建模塊提供抽象代理層,在架構層次對對象做 proxy。
Proxy并不一定要求保持接口完整的一致性,只要能夠實現間接控制,有時候損及一些透明性是可以接受的。
7. 適配器(Adapter)
將一個類的接口轉換成客戶希望的另一個接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。
——《設計模式》GoF
動機
在軟件系統中,由于應用環境的變化,常常需要將“一些現存的對象”放在新的環境中應用,但是新環境要求的接口是這些現存對象所不滿足的。
如何應對這種「遷移的變化」?如何既能利用現有對象的良好實現,同時又能滿足新的應用環境所要求的接口?
結構
筆記
系統的數據和行為都正確,但接口不符時,我們應該考慮用適配器,目的是使控制范圍之外的一個原有對象與某個接口匹配。適配器模式主要應用于希望復用一些現存的類,但是接口又與復用環境要求不一致的情況。
在 GoF 的設計模式中,講了兩種適配器:類適配器和對象適配器,由于類適配器模式通過多重繼承對一個接口與另一個接口進行匹配,而 C#、VB.NET、JAVA 等語言都不支持多重繼承(C++ 支持),也就是一個類只有一個父類,所以不推薦使用。
在雙方都不太容易修改的時候再使用適配器模式來適配,否則應該考慮用重構統一接口。
要點總結
Adapter 模式主要應用于“希望復用一些現存的類,但是接口又與復用環境要求不一致的情況”,在遺留代碼復用、類庫遷移等方面非常有用。
GoF 23 定義了兩種 Adapter 模式的結構實現:對象適配器和類適配器。但類適配器采用“多繼承”的實現方式,一般不推薦使用。對象適配器采用“對象組合”的方式,更符合松耦合精神。
Adapter 模式可以實現的非常靈活,不必拘泥于 GoF23 中定義的兩種結構。例如,完全可以將 Adapter 模式中的“現存對象”作為新的接口方法參數,來達到適配的目的。
8. 中介者(Mediator)
用一個中介對象來封裝(封裝變化)一系列的對象交互。中介者使各個對象不需要顯示的相互引用(編譯時依賴 -> 運行時依賴),從而使其耦合松散(管理變化),而且可以獨立的改變他們之間的交互。
——《設計模式》GoF
動機
在軟件構建過程中,經常會出現多個對象互相關聯交互的情況,對象之間常常會維持一種復雜的引用關系,如果遇到了一些需求的更改,這種直接的引用關系將面臨不斷的變化。
在這種情況下,我們可以使用一個“中介對象”來管理對象間的關聯關系,避免相互交互的對象之間的緊耦合引用關系,從而更好地抵御變化。
結構
筆記
盡管將一個系統分割成許多對象通常可以增加其可復用性,但是對象間相互連接的激增又會降低其可復用性了。
大量的連接使得一個對象不可能在沒有其他對象的支持下工作,系統表現為一個不可分割的整體,所以,對系統的行為進行任何較大的改動就十分困難了。中介者模式很容易在系統中應用,也很容易在系統中誤用。當系統出現了“多對多”交互復雜的對象群時,不要急于使用中介者模式,而要反思你的系統在設計上是不是合理。
-
中介者模式的優點:
- Mediator 的出現減少了各個 Colleague 的耦合,使得可以獨立地改變和復用各個 Colleague 類和 Mediator。
- 由于把對象如何協作進行了抽象,將中介作為一個獨立的概念并將其封裝在一個對象中,這樣關注的對象就從對象各自本身的行為轉移到它們之間的交互上來,也就是站在一個更宏觀的角度去看待系統。
中介者模式的缺點:
由于 ConcreteMediator 控制了集中化,于是就把交互復雜性變為了中介者的復雜性,這就使得中介者變得比任何一個 ConcreteColleague 都復雜。中介者模式一般應用于一組對象以定義良好但是復雜的方式進行通信的場合,以及想定制一個分布在多個類中的行為,而又不想生成太多的子類的場合。
要點總結
將多個對象間復雜的關聯關系解耦,Mediator 模式將多個對象間的控制邏輯進行集中管理,變“多個對象互相關聯”為“多個對象和一個中介者關聯”,簡化了系統的維護,抵御了可能的變化。
隨著控制邏輯的復雜化,Mediator 具體對象的實現可能相當復雜。這時候可以對
Mediator 對象進行分解處理。Facade 模式是解耦系統間(單向)的對象關聯關系;Mediator 模式是解耦系統內各個對象之間(雙向)的關聯關系。