設計模式中的開閉原則
Table of Contents
1 設計模式中的開閉原則
1.1 基本原則
系統的可擴展性由開-閉原則、里氏代換原則、依賴倒轉原則、組合/聚合復用原則保證;系 統的靈活性由開-閉原則、迪米特原則、接口隔離原則保證;系統的可插入性由開-閉原 則、里氏代換原則、依賴倒轉原則、組合/聚合復用原則保證。
當一個軟件復用有道、易於維護,新功能加入到系統,或修改一個已有的功能將是容易 的,因此,代碼高手就沒有用武之地;而當軟件是設計低劣、可維護性差,代碼高手就必 須用各種非常規的方式,連繼作戰,加班加點以達到目的。
2 模式中的開-閉原則
一個軟件應對擴展開放、對修改關閉,用head first中的話說就是:代碼應該如晚霞中 的蓮花一樣關閉(免於改變),如晨曦中的蓮花一樣開放(能夠擴展);英文原文:Software entities should open for extension, but closed for modification.
這個原則說的是,在設計一個模塊時,應當使這個模塊可以在不被修改的前提下被擴展, 換言之,應當可以在不必修改源代碼的情況下改變這個模塊的行為;因為所有軟件系統中 有一個共同的特性,即它們的需求都會隨時間的推移而發生變化,在軟件系統面臨新的需 求時,滿足開-閉原則的軟件中,系統已有模塊(特別是最重要的抽象層)不能再修改,而 通過擴展已有的模塊(特別是最重要的抽象層),可以提供新的行為,以滿足需求。
開-閉原則如果從另一個角度講述,就是所謂可變性封裝原則(Principle of Encapsulation of Variation,略寫作EVP),找到系統的可變因素,將之封裝起來。 用[GOF95]的話說:考慮你的設計中有什么可能發生變化,允許這些變化而不讓這些變化 導致重新設計。可變性封裝原則意味着:
- 一種可變性不應當散落在代碼的很多角落,而應當被封裝到一個對象中,同一種可變性 的不同表現可以體現在子類中,繼承應當被看做是封裝變化的方法,而不僅僅看做是從 父類派生子類
- 一種可變性不應當與另一種可變性混合在一起,所以一個設計模中,類圖的繼承層次 不會超過兩層,不然就意味着將兩種可變性混在一起
做到開閉原則不是件容易的事,但也很多規律可循,這些規律也同樣以設計原則的身份 出現,它們都是開-閉原則的手段和工具,是附屬於開-閉原則的。
2.1 策略模式
策略模式講的是,如果有一組算法,那么就將每一個算法封裝起來,使得它們可以互 換,顯然,策略模式就是從可變性的封裝原則出發,達到開-閉原則的一個范例。
這個模式完全支持開-閉原則。
策略模式
2.2 簡單工廠
簡單工廠模式中,開-閉原則要求允許新產品加入系統時,無需對業務類進行修改,但 需要修改工廠類。
這個模式並沒有很好的體現開-閉原則。
簡單工廠
2.3 工廠方法
工廠方法模式在業務類(Creator)中定義了一個創建對象的接口(FactoryMethod()),使 用繼承,由子類(ConcreteCreator)決定實例化的類是哪一個,工廠方法模式把實例化 推遲到子類。體現開-閉原則上,允許向系統加入新的產品類型,而不必修改已有代 碼,而只需擴展一個相應的具體業務類(ConcreteCreator);業務類(Creator)通常包含 依賴於抽象產品的代碼,而這些抽象產品由子類制造,業務類不需要真的知道哪種具體 產品。
業務類在一個方法中定義產品處理框架,其中的工廠方法一般是抽象方法,框架中調用 創建對象的方法(FactoryMethod())得到產品,由子類決定創建哪個對象。工廠模式有 點類似於模板模式,但含義不同,模板模式在一個方法中定義一個算法的骨架,其中算 法一般是抽象方法,而將一些實現步驟延遲到子類中;工廠方法返回的是生產出來的產 品,一般算法不需要返回對象。
這個模式完全支持開-閉原則。
工廠方法
2.4 抽象工廠
抽象工廠模式定義提供了一個接口(AbstractFactory),用於創建相關或依賴對象對象 的家族,而不需要明確指定具體類,抽象工廠模式封裝了產品對象家族的可變性,從而 一方面使系統動態決定產品家族產品實例化,另一方面可以在新產品引入到己有系統中 時不必修改已有系統。
抽象工廠與工廠方法定位上是完全不同的模式,抽象工廠提供一個產品家族的抽象類 型,這個類型的子類定義了產品被具體生成方法,要使用這個工廠,必須先實例化它, 傳入一些針對抽象類型所寫的代碼中,抽象工廠使用對象組合達到解耦;工廠方法通過 子類創建對象,父類只需知道抽象類型就可以了,工廠方法使用的是繼承達到解耦。
抽象工廠的實現上,具體的工廠繼承抽象工廠,一般用工廠方法(FactoryMethod)實 現,也可以用原型(Prototype)實現。
抽象工廠與策略模式類似,都是使用組合,策略模式中的算法可以互換,抽象工廠模式 中的工廠可以互換,它們的區別在於在於使用目的,策略重點是算法的實現,抽象工廠 是不同產品同一風格的實現。
這個模式完全支持開-閉原則。
抽象工廠
2.5 建造者模式
建造者模式將一個復雜對象的構建與它的表示分離,使得相同的構建過程可以創建不同 的表示,這個模式封裝了建造一個有內部結構的產品對象的過程,這樣的系統是向產品 內部表示的改變開放的。
建造者
建造者模式將構造代碼和表示代碼分開,它通過封裝一個復雜對象的創建和表示方式提 高了對象的模塊性,每個ConceteBuilder包含了創建和裝配一個特定產品的所有代碼, 然后不同的Director可以復用它以在相同部件組合的基礎上構建不同的Product。建造 者模式與一下子就生成產品的創建型模式不同,它是在Director的控制下一步一步構造 產品的,僅當該產品完成時,僅當該產品完成時,Director才從Builder中取回它。
這個模式完全支持開-閉原則。
2.6 橋梁模式
橋梁模式是"開-閉原則"極好例子,在橋梁模式用意是將抽象化(Abstraction)與實現化 (Implemention)解耦,使二者可以獨立地變化,從UML圖中可以看出,這個系統含有兩個 等級結構,也就是:
- 抽象化(Abstraction)角色和修正抽象化(RefinedAbstraction)角色組成的抽象化等級 結構
- 由實現化(Implementor)角色和具體實現化(ConcreteImplementor)角色所組成的實現 化等級結構
橋梁模式所涉及的角色有:
- 抽象化(Abstraction)角色:抽象化給出的定義,並保存一個對實現的引用
- 修正抽象化(RefinedAbstraction)角色:擴展抽象化角色,改變和修正對抽象化的定 義
- 實現化(Implementor)角色:實現化給出的定義,必須指出的是,這個接口和抽象化 接完全不一樣,實現化角色應當只給出底層操作,而抽象化角色應當給出基於底層操 作更高一層的操作
- 具體實現化(ConcreteImplementor)角色:給出實現化接口的具體實現
抽象化角色就象是一個水杯的手柄,而實現化角色相當於水杯的杯身,手柄控制杯身, 這就是此模式別名"柄體"的來源,如果用中國語言描述,應當是"綱目模式",而這兩個 等級是綱與目的關系,綱舉則目張。
橋梁模式
抽象化等級結構中的方法通過對應的實現化對象的委派實現自己的功能,抽象化角色可 以通過不同的實現化對象委派,達到動態轉換自己功能的目的。
橋梁模式和策略模式,這個模式類圖相似,但它們是用來解決完全不同問題的,策略是 關於算法的封裝,而橋梁模式是關於怎樣把抽象角色和實現角色的強耦合解除掉,橋梁 模式目的是要為同一個抽象化角色提供不同的實現。
2.7 外觀模式
外觀模式是將細粒度的對象包裝成粗粒度的對象,應用程序通過這個外觀對象來完成細 粒度對象的調用;假如一個系統開始的時候與某一個子系統耦合在一起,后來又不得不 換成另一個子系統,那么外觀模式可以發揮適配器的作用,將新子系統仍然與本系統耦 合在一起,這樣,外觀模式便可以改變子系統內部功能而不會影響到客戶端,也就是說 從客戶端代碼不用修改(對修改關閉),子系統可以更換(對擴展開放)。
外觀模式
在這個對象圖中,出現兩個角色:
- 外觀(Facade)角色:客戶端可以調用這個角色的方法,此角色知曉相關(多個對象)子 系統的功能和責任,本角色會將所有從客戶端發出來的請求委派到相應的子系統去
- 子系統(Subsystem Classes)角色:這是類的集合,每一個子系統可以被客戶端直接 調用,或者被外觀角色調用,子系統並不知道外觀的存在,對於子系統來說,外觀只 是另一個客戶端而己
2.8 中介模式
中介模式使用一個中介對象協調各個同事對象的相互作用,這些同事對象不再發生直接 的相互作用;這樣,一旦有新的同事類添加到系統中來,這些已有的同事對象不會受到 任何影響,但是中介對象本身卻需要修改;換言之中介模式並不是以一種完美方式支持 開-閉原則。
中介模式
2.9 迭代子模式
迭代子模代將訪問聚合元素的邏輯封裝起來,並且使它獨立於聚集對象的封裝,這就提 供了聚集存儲邏輯與迭代邏輯獨立演變的空間,使系統可以無需修改消費迭代子的客戶 端的情況下對聚集對象的內部結構進行擴展。這個模代完全支持開-閉原則。
中介模式