Design Patterns
前言
GoF的23種設計模式,包括創建型、結構型和行為型,其涵蓋了面向對象思想的精髓以及諸多細節。本文結合《設計模式》和《大話設計模式》,并用C++和Python實現了《大話設計模式》中的23種模式案例。
案例實現
創建型模式
工廠方法模式(Factory Method)
- 工廠方法模式:定義一個用于創建對象的接口,讓子類決定實例化哪個類。
- 工廠方法把簡單工廠的內部判斷邏輯移到了客戶端代碼,本來需要修改工廠類,現在是修改客戶端。
- 簡單工廠模式違背了開放-封閉原則,工廠方法模式借助多態,克服了該缺點,卻保持了封裝對象創建過程的優點。
抽象工廠模式(Abstract Factory)
- 抽象工廠模式:提供一個創建一系列相關或互相依賴對象的接口,只需要知道對象的系列,無需知道具體的對象。
- 在客戶端中,具體工廠類只在初始化時出現一次,更改產品系列即可使用不同產品配置。
- 利用簡單工廠類替換抽象工廠類及其子類,可以使客戶端不再受不同系列的影響。
結合反射機制,Assembly.Load(“程序集名稱”).CreateInstance(“命名空間”.“類名”),可以直接通過字符串創建對應類的實例。所有在簡單工廠中,都可以通過反射去除switch或if,解除分支判斷帶來的耦合。 - 反射中使用的字符串可以通過配置文件傳入,避免更改代碼。
單例模式(Singleton)
- 單例模式:讓類自身保證它只有一個實例,并提供一個全局訪問點。
- 多線程下單例模式可能失效,需要采取雙重鎖定的的方式,確保被鎖定的代碼同一時刻只被一個進程訪問。
- 餓漢式單例:即靜態初始化方式,在類初始化時產生私有單例對象,會提前占用資源;渴漢式單例:在第一次被引用時將自己初始化,會產生多線程訪問安全問題,需要添加雙重鎖定。
建造者模式(Builder)
- 建造者模式:將復雜對象的創建與表示分開,使得相同的創建過程可以有不同的表示。用戶只需制定需要建造的類型,不需要知道建造的過程和細節。
- 指揮者是建造者模式中重要的類,用于控制建造過程,也可以隔離用戶與建造過程的關聯。
- 建造者隱藏了產品的組裝細節,若需要改變一個產品的內部表示,可以再定義一個具體的建造者。
- 建造者模式是在當前創造復雜對象的算法,獨立于該對象的組成部分和裝配方式時適用的模式。
原型模式(Prototype)
- 原型模式:用原型實例指定創建對象的種類,并通過拷貝這些原型創建對象。本質是從一個對象再創建另一個可定制的對象,并且不需要知道創建細節。
- 原型抽象類的關鍵是有一個Clone()方法,原型具體類中復寫Clone()創建當前對象的淺表副本。
- 對.Net而言,由于拷貝太常用原型抽象類并不需要,在System命名空間中提供了ICloneable接口,其中唯一的方法就是Clone(),只要實現這個接口就可以完成原型模式。
- 原型拷貝無需重新初始化對象,動態獲取對象的運行狀態。既隱藏了對象創建的細節,又提升性能。
- 在具體原型類中,MemberwiseClone()方法是淺拷貝,對值類型字段諸位拷貝,對引用類型只復制引用但不會把具體的對象值拷貝過來。
- 比起淺拷貝,深拷貝把引用對象的變量指向新對象,而不是原被引用的對象。對于需要深拷貝的每一層,都需要實現ICloneable原型模式。
- 數據集對象DataSet,Clone()是淺拷貝,Copy()是深拷貝。
結構型模式
代理模式(Proxy)
- 代理模式:為其他對象提供一種代理以控制對這個對象的訪問。實際上是在訪問對象時引入一定程度的間接性。
- 遠程代理:為一個對象在不同地址空間提供局部代表,隱藏一個對象存在于不同空間的事實。如.Net加入Web引用,引入WebService,此時項目會生成WebReference的文件夾,就是代理。
- 虛擬代理:根據需要創建開銷很大的對象,通過它存放實例化需很長時間的真實對象。HTML中的多圖,就是通過虛擬代理代替了真實圖片,存儲路徑和尺寸。
- 安全代理:控制真實對象的訪問權限,用于對象應該擁有不同的訪問權限時。
- 智能指引:當調用真實對象時,代理處理一些另外的事情。通過代理在訪問對象時增加一些內務處理。
適配器模式(Adapter)
- 適配器模式:當系統數據和行為都一致,只有接口不符合時,將一個類的接口轉化為客戶端期望的另一個接口。
- 適配器模式用于服用一些現存的類,常用在第三方接口或軟件開發后期雙方都不易修改的時候。
- 在.Net中DataAdapter是用于DataSet和數據源間的適配器,Fill更改DataSet適配數據源,Update更改數據源適配DataSet。
外觀模式(Facade)
- 外觀模式:為子系統中一組接口提供一個一致的界面,即定義一個高層接口,增加子系統的易用性。
- 外觀模式完美體現了依賴倒轉原則和迪米特法則。
- 設計初期階段,在MVC三層架構中,任意兩層間建立外觀Facade。
- 子系統會因不斷演化變得復雜,增加外觀Facade提供簡單簡單接口減少依賴。
- 在維護一個大的遺留系統時,新的開發又必須依賴其部分功能。此時,開發一個外觀Facade類,從老系統中抽象出比較清晰的簡單接口。讓新系統只與Facade交互,而Facade與遺留代碼交互所有的工作。
裝飾模式(Decorator)
- 裝飾模式:動態的給一個對象添加一些額外的職能,把所需功能按順序串聯起來并進行控制。
- 每個要裝飾的功能放在單獨的類中,并讓這個類包裝它所要修飾的對象。當需要執行特殊行為時,客戶端就可以根據需要有選擇的、有順序的使用裝飾功能包裝對象了。
- 裝飾模式有效的把類的核心職能和裝飾功能區分開了,并且可以去除相關類中重復的裝飾邏輯。
橋接模式(Bridge)
- 對象的繼承關系編譯時已確定,所以無法在運行時修改從父類繼承的實現。由于緊耦合,父類中任何的改變必然會導致子類發生變化。當需要復用子類,但繼承下來的方法不合適時,必須重寫父類或用其他類替代。這種依賴性限制了靈活性和復用性。
- 合成/聚合復用原則:盡量使用合成和聚合而不是繼承。可以保證每個類封裝集中在單個任務上,不會出現規模太大的類及繼承結構。
- 橋接模式:抽象類和其派生類分離,各自實現自己的對象。若系統可以從多角度分類,且每種分類都可能變化,則把多角度分離獨立出來,降低耦合。
享元模式(Flyweight)
- 享元模式:運用共享技術有效支持大量細粒度對象。
- 在享元模式對象內部不隨環境改變的共享部分是內部狀態,不可共享需要通過調用傳遞進來的參數是外部狀態。
- 使用享元模式的場景包括,一個應用程序產生了大量的實例對象,占用了大量內存開銷;或對象的大多數狀態為外部狀態,刪除內部狀態后可以用較少的共享對象來取代組對象。
- 應用場景有正則表達式、瀏覽器、機器人指令集等。
組合模式(Composite)
- 組合模式:將對象的組合以樹形的層次結構表示,對單個對象和組合結構的操作具有一致性。
- 透明方法:葉子和分枝對外接口無差別;安全方法:分枝具有添加刪除葉子的接口,低層抽象接口和葉子沒有。
- 基本對象組合成組合,組合又可以被組合,不斷遞歸下去,在任何用到基本對象的地方都可以使用組合對象。
行為型模式
職責鏈模式(Chain of Responsibility)
- 職責鏈模式:使多個對象都有機會處理請求,解除請求發送者和接收者的耦合。將對象連成一條鏈,并沿這條鏈傳遞請求直到請求被解決。
- 請求交付給最小接受者,職責鏈中每一環保存后繼的引用,使得請求有序沿鏈傳遞。
通過合理設置后繼以及分支關系,避免一個請求到了鏈末端依舊無法被處理,或因配置錯誤得不到處理的情況。
策略模式(Strategy)
- 面向對象中并非類越多越好,類的劃分是為了封裝,但分類的基礎是抽象,具有相同屬性和功能的對象的抽象集合才是類。
- 策略模式:定義算法家族并分別封裝,他們完成的工作相同,只是實現不同,可以互相替換。繼承有助于析取這些算法的公共功能。此模式讓算法的變化不會影響到使用算法的用戶。
- 策略與工廠模式結合,使客戶端需要認識的類減少,耦合度更加降低。
- 策略模式可以簡化單元測試,因為每個算法可以通過自己的接口單獨測試。
- 只要在不同時間內應用不同的業務規則,就可以考慮用策略模式來處理這種變化的可能性。
狀態模式(State)
- 擁有過多分支的過長方法違背了單一職責原則,而且當需求變化時修改代碼往往會違背開放-封閉原則,應該將分支變成一不同小類,將狀態的判斷邏輯轉移到小類中。
- 狀態模式:一個對象可能擁有多種狀態,當內在狀態改變時允許改變行為。
- 狀態模式的好處是將與特定狀態有關的行為局部化,并將不同狀態的行為分隔開。
觀察者模式(Observer)
- 觀察者模式:多個觀察者對象同時監聽某一主題(通知者)對象,當該主題對象狀態變化時會通知所有觀察者對象,使它們能更新自己。
- 具體觀察者保存一個指向具體主題對象的引用,抽象主題保存一個抽象觀察者的引用集合,提供一個可以添加或刪除觀察者的接口。
- 抽象模式中有兩方面,一方面依賴另一方面,使用觀察者模式可將兩者獨立封裝,解除耦合。
- 觀察者模式讓主題和觀察者雙方都依賴于抽象接口,而不依賴于具體。
- 委托就是一種引用方法類型。委托可看作函數的類,委托的實例代表具體函數。在主題對象內聲明委托,不再依賴抽象觀察者。
一個委托可以搭載多個相同原形和形式(參數和返回值)的方法,這些方法不需要屬于一個類,且被依次喚醒。
迭代器模式(Iterator)
- 迭代器模式:提供一種方法順序遍歷一個聚集對象,為不同的聚集結構提供遍歷所需接口,而不暴露對象內部的表示。
- 在高級編程語言如c#、c++、java等,都已經把迭代器模式設計進語言的一部分。
- 迭代器模式分離了對象的遍歷行為,既不暴露內部結構又可以讓外部代碼透明的訪問集合內部的數據。
備忘錄模式(Memento)
- 備忘錄模式:不破壞封裝,獲取對象內部狀態并在其之外保存該對象,以便其未來恢復到當前狀態。
- Orginator負責創建Memento,Memento封裝Originator狀態細節,Caretaker負責保管和交付Memento。
- 備忘錄模式適用于需要維護歷史狀態的對象,或只需要保存原類屬性中的小部分。
命令模式(Command)
- 命令模式:將請求分裝為對象,將請求和執行分開,可以用不同的請求對客戶參數化。可以對請求排隊、通過或否決、記錄日志、撤銷或重做。
- 基于敏捷開發原則,不要給代碼添加基于猜測而實際不需要的功能,在需要的時候通過重構實現。
模板方法模式(Template Method)
- 模板方法模式:定義一個操作中的算法框架,將一些步驟延遲到子類中。子類在不改變框架的前提下就可以重新定義某些特定步驟。
- 當不變和可變的行為在子類中混到一起時,可以通過把重復的行為移到同一地方,幫助子類擺脫重復不變行為的糾纏。
中介者模式(Mediator)
- 中介者模式:用一個中介對象來封裝一系列對象間的交互。
- 中介者模式在系統中易用也容易被誤用,當系統中出現了多對多的交互復雜的對象群時,更應考慮設計的問題。
- 由于控制集中化,中介者模式將交互復雜性變成了中介者的復雜性,中介者類會比任何一個同事類都復雜。
- 中介者模式應用的場合有,一組對象以定義良好但復雜的方式進行通信,以及想定制一個分布在多個類中的行為卻不想產生太多子類。
解釋器模式(Interpreter)
- 解釋器模式:給定一種語言,定義它文法的一種表示,再定義一個解釋器,使用該表示來解釋語言中的句子。
- 如果一種特定類型發生的頻率足夠高,就可以將其實例表達為一個句子,構建解釋器來解析。
- 解釋器模式就是用“迷你語言”來表現程序要解決的問題,將句子抽象為語法樹。由于各個節電的類大體相同,便于修改、擴展和實現。
- 解釋器為文法中的每條規則定義了一個類,當文法過多時將難以維護,建議使用其他技術如語法分析程序或編譯器生成器處理。
訪問者模式(Visitor)
- 訪問者模式:在不改變各元素的前提下定義作用于這些類的新的操作。
- 訪問者模式使用雙分派,將數據結構和作用于結構上的操作解耦,意味著執行的操作決定于請求的種類和接收者的狀態。
- 如果系統具有較為穩定的數據結構,又有易于變化的算法操作,則適合使用訪問者模式。
對比總結
- 工廠方法模式:為不同子類創建不同工廠;
- 抽象工廠模式:為不同系列建造不同工廠;
- 單例模式:保證實例唯一;
- 建造者模式:為不同類組裝出一套相同的方法;
- 原型模式:實現深拷貝。
- 代理模式:控制訪問;
- 適配器模式:將接口轉換為客戶端期望的形式;
- 外觀模式:整理出一套可用接口;
- 裝飾模式:動態修改類的職能;
- 橋接模式:將多角度分類分離獨立;
- 享元模式:共享實例;
- 組合模式:遞歸生成樹形結構的組合對象。
- 職責鏈模式:按順序讓負責的對象們依次處理;
- 策略模式:將算法族抽象封裝;
- 狀態模式:將復雜的狀態轉移方式下發到每個狀態內部;
- 觀察者模式:發布和訂閱;
- 迭代器模式:遍歷容器;
- 備忘錄模式:在對象之外備份及恢復;
- 命令模式:封裝請求與執行分開;
- 模板方法模式:提煉共有方法。
- 中介者模式:用中介對象封裝交互。
- 解釋器模式:迷你語言;
- 訪問者模式:解耦數據結構及操作。