- 0 前言
- 1 創建型模式
- 2 結構型模式
- 3 行爲型模式
0) 前言
0-1) 設計模式的六大原則
總原則:開閉原則(Open Close Principle)
開閉原則就是說對擴展開放,對修改關閉。在程序需要進行拓展的時候,不能去修改原有的代碼,而是要擴展原有代碼,實現一個熱插拔的效果。所以一句話概括就是:爲了使程序的擴展性好,易於維護和升級。想要達到這樣的效果,我們需要使用接口和抽象類等,後面的具體設計中我們會提到這點。
0-1-1) 單一職責原則 (Single responsibility principle)
不要存在多於一個導致類變更的原因,也就是說每個類應該實現單一的職責,如若不然,就應該把類拆分。
0-1-2) 里氏替換原則 (Liskov Substitution Principle)
里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。 LSP是繼承複用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被複用,而衍生類也能夠在基類的基礎上增加新的行爲。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規範。
歷史替換原則中,子類對父類的方法儘量不要重寫和重載。因爲父類代表了定義好的結構,通過這個規範的接口與外界交互,子類不應該隨便破壞它。
0-1-3) 依賴倒轉原則 (Dependence Inversion Principle)
這個是開閉原則的基礎,具體內容:面向接口編程,依賴於抽象而不依賴於具體。寫代碼時用到具體類時,不與具體類交互,而與具體類的上層接口交互。
0-1-4) 接口隔離原則 (Interface Segregation Principle)
這個原則的意思是:每個接口中不存在子類用不到卻必須實現的方法,如果不然,就要將接口拆分。使用多個隔離的接口,比使用單個接口(多個接口方法集合到一個的接口)要好。
0-1-5) 迪米特法則(最少知道原則) (Demeter Principle)
就是說:一個類對自己依賴的類知道的越少越好。也就是說無論被依賴的類多麼複雜,都應該將邏輯封裝在方法的內部,通過public方法提供給外部。這樣當被依賴的類變化時,才能最小的影響該類。
最少知道原則的另一個表達方式是:只與直接的朋友通信。類之間只要有耦合關係,就叫朋友關係。耦合分爲依賴、關聯、聚合、組合等。我們稱出現爲成員變量、方法參數、方法返回值中的類爲直接朋友。局部變量、臨時變量則不是直接的朋友。我們要求陌生的類不要作爲局部變量出現在類中。
0-1-6)合成複用原則 (Composite Reuse Principle)
原則是儘量首先使用合成/聚合的方式,而不是使用繼承。
0-2) 設計模式分類
名稱 | 創建型模式 Creational Pattern | 結構型模式 Structural Pattern | 行爲型模式 Behavioral Pattern |
概念 | 創建型模式,就是創建對象的模式,抽象了實例化的過程。它幫助一個系統獨立於如何創建、組合和表示它的那些對象。關注的是對象的創建,創建型模式將創建對象的過程進行了抽象,也可以理解爲將創建對象的過程進行了封裝,作爲客戶程序僅僅需要去使用對象,而不再關心創建對象過程中的邏輯 | 結構型模式是爲解決怎樣組裝現有的類,設計他們的交互方式,從而達到實現一定的功能的目的。結構型模式包容了對很多問題的解決。例如:擴展性(外觀、組成、代理、裝飾)封裝性(適配器,橋接) | 行爲型模式涉及到算法和對象間職責的分配,行爲模式描述了對象和類的模式,以及它們之間的通信模式,行爲型模式刻劃了在程序運行時難以跟蹤的複雜的控制流可分爲行爲類模式和行爲對象模式1.行爲模式使用繼承機制在類間分派行爲2.行爲對象模式使用對象聚合來分配行爲。一些行爲對象模式描述了一組對等的對象怎樣相互協作以完成其中任何一個對象都無法單獨完成的任務。 |
類 | Factory Method | Adapter(類) | Interpreter |
Template Method | |||
類 | Abstract Factory | Adapter(對象) | Chain of Responsibility |
Builder | Bridge | Command | |
Prototype | Composite | Iterator | |
Singleton | Decorator | Mediator | |
Facade | Memento | ||
Flyweight | Observer | ||
Proxy | State | ||
Strategy | |||
Visitor |
1) 創建型模式
1-1) Factory 模式
1-1-1) 定義
定義一個用於創建對象的接口,讓子類決定實例化哪一個類。Factory Method 使一個類的實例化延遲到其子類。
1-1-2) 詳解
第一種功能,定義接口,Factory 的結構示意圖:
1-1-3) 場景
- 當一個類不知道它所必須創建的對象的類的時候。
- 當一個類希望由它的子類來指定它所創建的對象的時候。
- 當類將創建對象的職責委託給多個幫助子類中的某一個,並且你希望將哪一個幫助子類是代理者這一信息局部化的時候。
1-1-4) 總結
Factory 模式在實際開發中應用非常廣泛,面向對象的系統經常面臨着對象創建問題: 要創建的類實在是太多了。而 Factory 提供的創建對象的接口封裝(第一個功能),以及其 將類的實例化推遲到子類(第二個功能)都部分地解決了實際問題。
但這不是Factory功能的最大優點,真正的優點是第二個功能,子類的延遲實例化,如下圖:
圖 2 中關鍵中 Factory 模式的應用並不是只是爲了封裝對象的創建,而是要把對象的創
建放到子類中實現:Factory 中只是提供了對象創建的接口,其實現將放在 Factory 的子類 ConcreteFactory 中進行。這是圖 2 和圖 1 的區別所在。
1-2) AbstractFactory 模式
1-2-1) 定義
AbstractFactory 模式就是提供一個創建一系列相關或相互依賴對象的接口,而無需指定它們具體的類。
1-2-2) 詳解
AbstractFactory 模式典型的結構圖爲:
- AbstractFactory (抽象工廠)
- 聲明一個創建抽象產品對象的操作接口。
- ConcreteFactory (具體工廠)
- 實現創建具體產品對象的操作。
- AbstractProduct (抽象產品)
- 爲一類產品對象聲明一個接口。
- ConcreteProduct (具體產品)
- 定義一個將被相應的具體工廠創建的產品對象。
- 實現AbstractProduct接口。
- Client(使用場景)
- 僅使用由AbstractFactory和AbstractProduct類聲明的接口。
1-2-3) 場景
• 一個系統要獨立於它的產品的創建、組合和表示時。
• 一個系統要由多個產品系列中的一個來配置時。
• 當你要強調一系列相關的產品對象的設計以便進行聯合使用時。
• 當你提供一個產品類庫,而只想顯示它們的接口而不是實現時。
1-2-4) 總結
工廠方法模式和抽象工廠模式的區別
簡單工廠模式:
- 只有一個工廠類一個生產方法,根據參數不同生產不同的產品。
工廠方法模式:
- 每一個工廠類只負責一個產品生產,不生成其它產品。好比一條生產線只生產一個產品線。
抽象工廠模式:
- 每一個工廠類提供多個方法,可以生產不同的產品。好比多條生產線可以生產多家產品。
1-3) Singleton 模式
1-3-1) 定義
保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
1-3-2) 詳解
Singleton 模式典型的結構圖爲:
在 Singleton 模式的結構圖中可以看到,我們通過維護一個 static 的成員變量來記錄這
個唯一的對象實例。通過提供一個 staitc 的接口 instance 來獲得這個唯一的實例。
1-3-3) 場景
- 當類只有一個實例而且客戶可以從一個衆所周知的訪問點訪問它時。
- 當這個唯一實例應該是通過子類化可擴展的,並且客戶應該無需更改代碼就能使用一個擴展的實例時。
1-3-4) 總結
開發中使用單例模式,有幾點要注意:
1. 只創建一個實例,並且只提供一個全局的訪問點;避免創建多個實例的可能。
2. 資源共享情況下,獲取實例的方法必須適應多線程併發訪問。
3. 提高訪問性能。
4. 懶加載(Lazy Load),在需要的時候才被構造。。
1-4) Builder 模式
1-4-1) 定義
將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。
1-4-2) 詳解
Builder 模式的典型結構圖爲:
- Builder(抽象建造者角色)
- 爲創建一個Product對象的各個部件指定抽象接口。
- ConcreteBuilder(具體建造者)
- 實現Builder的接口以構造和裝配該產品的各個部件。
- 定義並明確它所創建的表示。
- 提供一個檢索產品的接口。
- Director(導演角色)
- 構造一個使用Builder接口的對象。
- Product(建造的產品)
- 表示被構造的複雜對象。 ConcreteBuilder創建該產品的內部表示並定義它的裝配過程。
- 包含定義組成部件的類,包括將這些部件裝配成最終產品的接口。
1-4-3) 場景
- 當創建複雜對象的算法應該獨立於該對象的組成部分以及它們的裝配方式時。
- 當構造過程必須允許被構造的對象有不同的表示時。
1-4-4) 總結
當創造一個對象需要很多步驟時適合使用建造者模式。
而當只需調用一個方法就可以簡單地創建整個對象時適合使用工廠模式。
1-5) Prototype 模式
1-5-1) 定義
用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。
1-5-2) 詳解
- Prototype(原型): 聲明一個克隆自身的接口。
- ConcretePrototype(具體的原型): 實現一個克隆自身的操作。
- Client(場景):讓一個原型克隆自身從而創建一個新的對象。
1-5-3) 場景
• 當要實例化的類是在運行時刻指定時,例如,通過動態裝載;
• 爲了避免創建一個與產品類層次平行的工廠類層次時;
• 當一個類的實例只能有幾個不同狀態組合中的一種時。建立相應數目的原型並克隆它們可能比每次用合適的狀態手工實例化該類更方便一些。
1-5-4) 總結
Prototype 模式通過複製原型(Prototype)而獲得新對象創建的功能,這裏 Prototype 本身就是“對象工廠”(因爲能夠生產對象),實際上 Prototype 模式和 Builder 模式、 AbstractFactory 模式都是通過一個類(對象實例)來專門負責對象的創建工作(工廠對象), 它們之間的區別是:
Builder 模式重在複雜對象的一步步創建(並不直接返回對象)
AbstractFactory 模式重在產生多個相互依賴類的對象
Prototype 模式重在從自身複製自己創建新類。
1-5-4-1) 優點:
性能優良。原型模式是在內存二進制流的拷貝,要比直接new一個對象性能好很多,特別是要在一個循環體內產生大量的對象時,原型模式可以更好地體現其優點。
1-5-4-2) 缺點:
逃避構造函數的約束。這既是它的優點也是缺點,直接在內存中拷貝,構造函數是不會執行的。優點就是減少了約束,缺點也是減少了約束,需要大家在實際應用時考慮。
2) 結構型模式
2-1) Bridge 模式
2-1-1) 定義
將抽象部分與它的實現部分分離,使它們都可以獨立地變化。
2-1-2) 詳解
Bridge 模式典型的結構圖爲:
- 抽象類(Abstraction):定義抽象類的接口,維護一個指向AbstractionImpl類型對象的指針
- 擴充抽象類(RefinedAbstraction):擴充由Abstraction定義的接口
- 實現類接口(AbstractionImpl):定義實現類的接口,該接口不一定要與Abstraction的接口完全一致;事實上這兩個接口可以完全不同。一般來講, AbstractionImpl接口僅提供基本操作,而 Abstraction則定義了基於這些基本操作的較高層次的操作。
- 具體實現類(ConcreteAbstraction):實現AbstractionImpl接口並定義它的具體實現。
理解橋接模式,重點需要理解如何將抽象化(Abstraction)與實現化(Implementation)脫耦,使得二者可以獨立地變化。
- 抽象化:抽象化就是忽略一些信息,把不同的實體當作同樣的實體對待。在面向對象中,將對象的共同性質抽取出來形成類的過程即爲抽象化的過程。
- 實現化:針對抽象化給出的具體實現,就是實現化,抽象化與實現化是一對互逆的概念,實現化產生的對象比抽象化更具體,是對抽象化事物的進一步具體化的產物。
- 脫耦:脫耦就是將抽象化和實現化之間的耦合解脫開,或者說是將它們之間的強關聯改換成弱關聯,將兩個角色之間的繼承關係改爲關聯關係。橋接模式中的所謂脫耦,就是指在一個軟件系統的抽象化和實現化之間使用關聯關係(組合或者聚合關係)而不是繼承關係,從而使兩者可以相對獨立地變化,這就是橋接模式的用意。
2-1-3) 實例
2-1-3-1) 筆和顏料
現需要提供大中小3種型號的畫筆,能夠繪製5種不同顏色,如果使用蠟筆,我們需要準備3*5=15支蠟筆,也就是說必須準備15個具體的蠟筆類。而如果使用毛筆的話,只需要3種型號的毛筆,外加5個顏料盒,用3+5=8個類就可以實現15支蠟筆的功能。
實際上,蠟筆和毛筆的關鍵一個區別就在於筆和顏色是否能夠分離。即將抽象化(Abstraction)與實現化(Implementation)脫耦,使得二者可以獨立地變化”。關鍵就在於能否脫耦。蠟筆的顏色和蠟筆本身是分不開的,所以就造成必須使用15支色彩、大小各異的蠟筆來繪製圖畫。而毛筆與顏料能夠很好的脫耦,各自獨立變化,便簡化了操作。在這裏,抽象層面的概念是:”毛筆用顏料作畫”,而在實現時,毛筆有大中小三號,顏料有紅綠藍黑白等5種,於是便可出現3×5種組合。每個參與者(毛筆與顏料)都可以在自己的自由度上隨意轉換。
蠟筆由於無法將筆與顏色分離,造成筆與顏色兩個自由度無法單獨變化,使得只有創建15種對象才能完成任務。
Bridge模式將繼承關係轉換爲組合關係,從而降低了系統間的耦合,減少了代碼編寫量。
UML如圖:
2-1-3-2) 跨平臺視頻播放器:兩個維度的變化,平臺和不同格式的視頻文件:
2-1-4) 總結
優點:
- 分離接口及其實現部分 一個實現未必不變地綁定在一個接口上。抽象類的實現可以在運行時刻進行配置,一個對象甚至可以在運行時刻改變它的實現。將Abstraction與Implementor分離有助於降低對實現部分編譯時刻的依賴性,當改變一個實現類時,並不需要重新編譯 Abstraction類和它的客戶程序。爲了保證一個類庫的不同版本之間的二進制兼容性,一定要有這個性質。另外,接口與實現分離有助於分層,從而產生更好的結構化系統,系統的高層部分僅需知道Abstraction和Implementor即可。
- 提高可擴充性 你可以獨立地對Abstraction和Implementor層次結構進行擴充。
- 實現細節對客戶透明 你可以對客戶隱藏實現細節,例如共享 Implementor對象以及相應的引用計數機制(如果有的話) 。
橋接模式的缺點:
- 橋接模式的引入會增加系統的理解與設計難度,由於聚合關聯關係建立在抽象層,要求開發者針對抽象進行設計與編程。
- 橋接模式要求正確識別出系統中兩個獨立變化的維度,因此其使用範圍具有一定的侷限性。
簡而言之:橋接模式是分離抽象化和實現,使兩者的接口可以不同,目的是分離,使兩個或兩個以上的維度任意組合。每個維度的修改互不影響。
2-2) Adapter 模式
2-2-1) 定義
Adapter 模式: 將一個類的接口轉換成客戶希望的另一個接口。Adapter模式使得原本由於接口不兼容而不能一起工作的類可以一起工作。
2-2-2) 詳解
Adapter 模式典型的結構圖爲:
Target (目標角色,要用的接口)
定義Client使用的與特定領域相關的接口。Client (使用場景)
與符合Target接口的對象協同。Adaptee (被適配者)
定義一個已經存在的接口,這個接口需要適配。Adapter (適配器,把Adaptee接口轉換爲Target可用的接口)
對Adaptee的接口與Target接口進行適配
2-2-3) 場景
- 你想使用一個已經存在的類,而它的接口不符合你的需求。
- 你想創建一個可以複用的類,該類可以與其他不相關的類或不可預見的類(即那些接口可能不一定兼容的類)協同工作。
- (僅適用於對象Adapter)你想使用一些已經存在的子類,但是不可能對每一個都進行子類化以匹配它們的接口。對象適配器可以適配它的父類接口。
2-2-4) 總結
優點:
1. 適配器模式可以讓兩個沒有任何關係的類在一起運行,只要適配器這個角色能夠搞定他們就成。
2. 增加了類的透明性
想想看,我們訪問的Target目標角色,但是具體的實現都委託給了源角色,而這些對高層次模塊是透明的,也是它不需要關心的。
3. 提高了類的複用度當然了,源角色在原有的系統中還是可以正常使用,而在目標角色中也可以充當新的演員。
4. 靈活性非常好
某一天,突然不想要適配器,沒問題,刪除掉這個適配器就可以了,其他的代碼都不用修改,基本上就類似一個靈活的構件,想用就用,不想就卸載。
2-3) Decorator 模式
2-3-1) 定義
動態地給一個對象添加一些額外的職責。就增加功能來說,Decorator模式相比生成子類更爲靈活。
2-3-2) 詳解
Decorator Pattern叫裝飾模式,或裝飾者模式,以前叫包裝器模式(Wrapper,GoF在92-93年由Wrapper改爲Decorator)。
裝飾模式是在不必改變原類文件和使用繼承的情況下,動態地擴展一個對象的功能。它是通過創建一個包裝對象,也就是裝飾來包裹真實的對象。
Decorator 模式典型的結構圖爲:
- 抽象構件(Component)角色:給出一個抽象接口,以規範準備接收附加責任的對象。
- 具體構件(Concrete Component)角色:定義一個將要接收附加責任的類。
- 裝飾(Decorator)角色:持有一個構件(Component)對象的實例,並實現一個與抽象構件接口一致的接口。
- 具體裝飾(Concrete Decorator)角色:負責給構件對象添加上附加的責任。
2-3-3) 場景
- 需要擴展一個類的功能,或給一個類添加附加職責。
- 需要動態的給一個對象添加功能,這些功能可以再動態的撤銷。
- 需要增加由一些基本功能的排列組合而產生的非常大量的功能,從而使繼承關係變的不現實。
- 當不能採用生成子類的方法進行擴充時。一種情況是,可能有大量獨立的擴展,爲支持每一種組合將產生大量的子類,使得子類數目呈爆炸性增長。另一種情況可能是因爲類定義被隱藏,或類定義不能用於生成子類。
2-3-4) 總結
Decorator 模式除了採用組合的方式取得了比採用繼承方式更好的效果,Decorator 模式 還給設計帶來一種“即用即付”的方式來添加職責。在 OO 設計和分析經常有這樣一種情況: 爲了多態,通過父類指針指向其具體子類,但是這就帶來另外一個問題,當具體子類要添加 新的職責,就必須向其父類添加一個這個職責的抽象接口,否則是通過父類指針是調用不到 這個方法了。這樣處於高層的父類就承載了太多的特徵(方法),並且繼承自這個父類的所 有子類都不可避免繼承了父類的這些接口,但是可能這並不是這個具體子類所需要的。而在 Decorator 模式提供了一種較好的解決方法,當需要添加一個操作的時候就可以通過 Decorator 模式來解決,你可以一步步添加新的職責。
2-4) Composite 模式
2-4-1) 定義
將對象組合成樹形結構以表示“部分-整體”的層次結構。組合模式使得用戶對單個對象和組合對象的使用具有一致性。
2-4-2) 詳解
Composite 模式的典型結構圖爲:
- Component 是組合中的對象聲明接口,在適當的情況下,實現所有類共有接口的默認行爲。聲明一個接口用於訪問和管理Component子部件。
- Leaf 在組合中表示葉子結點對象,葉子結點沒有子結點。
- Composite 定義有枝節點行爲,用來存儲子部件,在Component接口中實現與子部件有關操作,如增加(add)和刪除(remove)等。
2-4-3) 場景
1.你想表示對象的部分-整體層次結構
2.你希望用戶忽略組合對象與單個對象的不同,用戶將統一地使用組合結構中的所有對象
2-4-4) 總結
Composite 模式通過和 Decorator 模式有着類似的結構圖,但是 Composite 模式旨在構造 類,而 Decorator 模式重在不生成子類即可給對象添加職責。Decorator 模式重在修飾,而 Composite 模式重在表示。
2-5) Flyweight 模式
2-5-1) 定義
運用共享技術有效地支持大量細粒度的對象。
2-5-2) 詳解
有些應用程序得益於在其整個設計過程中採用對象技術,但簡單化的實現代價極大。
使用面向對象的抽象化,可能會造成龐大的對象羣,造成空間的巨大消耗,而影響性能。
在文檔編輯器例子中如果一個字符對應一個對象,那麼一篇文檔所要容納的對象將是非常的龐大耗費大量的內存。
而實際組成文檔的字符是有限的,是由這些字符不同的組合和排列得到的。
所以需要共享,將基本的字符進行共享,將使得字符對象變得有限。
Flyweight只存儲相應的字符代碼
這裏的關鍵概念是內部狀態和外部狀態之間的區別。
內部狀態存儲於flyweight中,它包含了獨立於flyweight場景的信息,這些信息使得flyweight可以被共享。
如字符代碼,字符大小……
外部狀態取決於flyweight場景,並根據場景而變化,因此不可共享。用戶對象負責在必要的時候將外部狀態傳遞給flyweight。
如字符位置,字符顏色……
典型的結構圖爲:
下面的對象圖說明了如何共享 Flyweight:
Flyweight
- 描述一個接口,通過這個接口 Flyweight 可以接受並作用於外部狀態。
ConcreteFlyweight
- 實現 Flyweight 接口,併爲內部狀態增加存儲空間。該對象必須是可共享的。它所存儲的狀態必須是內部的,即必須獨立於對象的場景。
UnsharedConcreteFlyweight
- 並非所有的 Flyweight 子類都需要被共享。Flyweight 接口使共享成爲可能,但它並不強制共享。
FlyweightFactory
- 創建並管理 Flyweight 對象。
確保合理地共享 Flyweight。
Client
- 維持一個對 Flyweight 的引用。
計算或存儲 Flyweight 的外部狀態。
2-5-3) 適用性
Flyweight 模式的有效性很大程度上取決於如何使用它以及在何處使用它。
當以下情況成立時可以使用 Flyweight 模式:
- 一個應用程序使用了大量的對象。
- 完全由於使用大量對象,造成很大的存儲開銷。
- 對象的大多數狀態都可變爲外部狀態。
- 如果刪除對象的外部狀態,那麼可以用相對較少的共享對象取代很多組對象。
- 應用程序不依賴於對象標識。由於Flyweight對象可以被共享,對於概念上明顯有別的對象,標識測試將返回真值
2-5-4) 總結
Flyweight 模式通常和 Composite 模式結合起來,用共享葉節點的又向無環圖實現一個邏輯上的層次結構。
通常,最好用 Flyweight 實現 State 和 Strategy 對象。
2-6) Facade 模式
2-6-1) 定義
爲子系統中的一組接口提供一個一致的界面,Facade模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。
2-6-2) 詳解
我們通過 Facade 模式解決上面的問題,其典型的結構圖爲:
2-6-3) 場景
- 當你要爲一個複雜子系統提供一個簡單接口時。子系統往往因爲不斷演化而變得越來越複雜。大多數模式使用時都會產生更多更小的類。這使得子系統更具可重用性, 也更容易對子系統進行定製,但這也給那些不需要定製子系統的用戶帶來一些使用上的困難。Facade可以提供一個簡單的缺省視圖,這一視圖對大多數用戶來 說已經足夠,而那些需要更多的可定製性的用戶可以越過Facade層。
- 客戶程序與抽象類的實現部分之間存在着很大的依賴性。引入Facade將這個子系統與客戶以及其他的子系統分離,可以提高子系統的獨立性和可移植性。
- 當你需要構建一個層次結構的子系統時,使用門面模式定義子系統中每層的入口點。如果子系統之間是相互依賴的,你可以讓它們僅通過Facade進行通訊,從而簡化了它們之間的依賴關係。
2-6-4) 總結
Facade模式有下面一些優點:
1. 對客戶屏蔽子系統組件,減少了客戶處理的對象數目並使得子系統使用起來更加容易。通過引入外觀模式,客戶代碼將變得很簡單,與之關聯的對象也很少。
2. 實現了子系統與客戶之間的松耦合關係,這使得子系統的組件變化不會影響到調用它的客戶類,只需要調整外觀類即可。
3. 降低了大型軟件系統中的編譯依賴性,並簡化了系統在不同平臺之間的移植過程,因爲編譯一個子系統一般不需要編譯所有其他的子系統。一個子系統的修改對其他子系統沒有任何影響,而且子系統內部變化也不會影響到外觀對象。
4. 只是提供了一個訪問子系統的統一入口,並不影響用戶直接使用子系統類。
Facade模式的缺點
1. 不能很好地限制客戶使用子系統類,如果對客戶訪問子系統類做太多的限制則減少了可變性和靈活性。
2. 在不引入抽象外觀類的情況下,增加新的子系統可能需要修改外觀類或客戶端的源代碼,違背了“開閉原則”。
2-7) Proxy 模式
2-7-1) 定義
爲其他對象提供一種代理以控制對這個對象的訪問。
2-7-2) 詳解
Proxy 模式典型的結構圖爲:
實際上,Proxy 模式的想法非常簡單
2-7-3) 場景
在需要用比較通用和複雜的對象指針代替簡單的指針的時候,使用Proxy模式。下面是一些可以使用Proxy模式常見情況:
遠程代理(Remote Proxy)爲一個對象在不同的地址空間提供局部代表。
虛代理(Virtual Proxy)根據需要創建開銷很大的對象。
保護代理(Protection Proxy)控制對原始對象的訪問。保護代理用於對象應該有不同 的訪問權限的時候。
智能指引(Smart Reference)取代了簡單的指針,它在訪問對象時執行一些附加操作。 它的典型用途包括:
- 對指向實際對象的引用計數,這樣當該對象沒有引用時,可以自動釋放它(也稱爲SmartPointers)。
- 當第一次引用一個持久對象時,將它裝入內存。
- 在訪問一個實際對象前,檢查是否已經鎖定了它,以確保其他對象不能改變它。
2-7-4) 總結
Proxy 模式最大的好處就是實現了邏輯和實現的徹底解耦。
Proxy 模式是增加訪問控制,典型Spring,AOC。
Decorator 模式是增強子類。
3) 行爲型模式
3-1) Template 模式
3-1-1) 定義
定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。Template Method使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。
3-1-2) 詳解
一個通用的 Template 模式的結構圖爲:
AbstractClass(抽象類,如Application)
- 定義抽象的原語操作(primitive operation),具體的子類將重定義它們以實現一個算法的各步驟。
- 實現一個模板方法 ,定義一個算法的骨架。該模板方法不僅調用原語操作,也調用定義 在AbstractClass或其他對象中的操作。
ConcreteClass(具體類,如MyApplication)
- 實現原語操作以完成算法中與特定子類相關的步驟
3-1-3) 場景
- 一次性實現一個算法的不變的部分,並將可變的行爲留給子類來實現。
- 各子類中公共的 行爲應被提取出來並集中到一個公共父類中以避免代碼重複。這是Opdyke和Johnson所描述過的“重分解以一般化”的一個很好的例子。首先識別現有 代碼中的不同之處,並且將不同之處分離爲新的操作。最後,用一個調用這些新的操作的模板方法來替換這些不同的代碼。
- 控制子類擴展。模板方法只在特定點調用“hook”操作,這樣就只允許在這些點進行擴展。
3-1-4) 總結
優點:
封裝不變部分,擴展可變部分
把認爲是不變部分的算法封裝到父類實現,而可變部分的則可以通過繼承來繼續擴展。在悍馬模型例子中,是不是就非常容易擴展?例如增加一個H3型號的悍馬模型,很容易呀,增加一個子類,實現父類的基本方法就可以了。提取公共部分代碼,便於維護
我們例子中剛剛走過的彎路就是最好的證明,如果我們不抽取到父類中,任由這種散亂的代碼發生,想想後果是什麼樣子?維護人員爲了修正一個缺陷,需要到處查找類似的代碼!行爲由父類控制,子類實現
基本方法是由子類實現的,因此子類可以通過擴展的方式增加相應的功能,符合開閉原則。
缺點:
- 按照我們的設計習慣,抽象類負責聲明最抽象、最一般的事物屬性和方法,實現類完成具體的事物屬性和方法。但是模板方法模式卻顛倒了,抽象類定義了部分抽象方法,由子類實現,子類執行的結果影響了父類的結果,也就是子類對父類產生了影響,這在複雜的項目中,會帶來代碼閱讀的難度,而且也會讓新手產生不適感。
3-2) Strategy 模式
3-2-1) 定義
定義一系列的算法,把它們一個個封裝起來, 並且使它們可相互替換。本模式使得算法可獨立於使用它的客戶而變化。
3-2-2) 詳解
Strategy 模式典型的結構圖爲:
Strategy(策略)
- 定義所有支持的算法的公共接口。
Context使用這個接口來調用某個ConcreteStrategy定 義的算法。
- 定義所有支持的算法的公共接口。
ConcreteStrategy(具體策略)
- 以Strategy接口實現某具體算法。
Context(上下文)?
- 用一個ConcreteStrategy對象來配置。
- 維護一個對Strategy對象的引用。
- 可定義一個接口來讓Stategy訪問它的數據。
3-2-3) 場景
- 許多相關的類僅僅是行爲有異。“策略”提供了一種用多個行爲中的一個行爲來配置一個類的方法。
- 需要使用一個算法的不同變體。例如,你可能會定義一些反映不同的空間/時間權衡的算法。當這些變體實現爲一個算法的類層次時,可以使用策略模式。
- 算法使用客戶不應該知道的數據。可使用策略模式以避免暴露覆雜的、與算法相關的數據結構。
- 一個類定義了多種行爲, 並且這些行爲在這個類的操作中以多個條件語句的形式出現。將相關的條件分支移入它們各自的Strategy類中以代替這些條件語句。
3-2-4) 總結
優點:
算法可以自由切換
這是策略模式本身定義的,只要實現抽象策略,它就成爲策略家族的一個成員,通過封裝角色對其進行封裝,保證對外提供“可自由切換”的策略。避免使用多重條件判斷
如果沒有策略模式,我們想想看會是什麼樣子?一個策略家族有5個策略算法,一會要使用A策略,一會要使用B策略,怎麼設計呢?使用多重的條件語句?多重條件語句不易維護,而且出錯的概率大大增強。使用策略模式後,可以由其他模塊決定採用何種策略,策略家族對外提供的訪問接口就是封裝類,簡化了操作,同時避免了條件語句判斷。擴展性良好
這甚至都不用說是它的優點,因爲它太明顯了。在現有的系統中增加一個策略太容易了,只要實現接口就可以了,其他都不用修改,類似於一個可反覆拆卸的插件,這大大地符合了OCP原則。
缺點:
策略類數量增多
每一個策略都是一個類,複用的可能性很小,類數量增多。所有的策略類都需要對外暴露
上層模塊必須知道有哪些策略,然後才能決定使用哪一個策略,這與迪米特法則是相違背的,我只是想使用了一個策略,我憑什麼就要了解這個策略呢?那要你的封裝類還有什麼意義?這是原裝策略模式的一個缺點,幸運的是,我們可以使用其他模式來修正這個缺陷,如工廠方法模式、代理模式或享元模式。
3-3) State 模式
3-3-1) 定義
允許一個對象在其內部狀態改變時改變它的行爲。對象看起來似乎修改了它的類。
3-3-2) 詳解
State 模式將每一個分支都封裝到獨立的類中。State 模式典型的結構圖爲:
Context(環境角色)
— 定義客戶感興趣的接口。
— 維護一個ConcreteState子類的實例,這個實例定義當前狀態。State(抽象狀態角色)
— 定義一個接口以封裝與 Context的一個特定狀態相關的行爲。ConcreteState subclasses(具體狀態角色)
— 每一子類實現一個與Context的一個狀態相關的行爲。
3-3-3) 場景
- 一個對象的行爲取決於它的狀態, 並且它必須在運行時刻根據狀態改變它的行爲。
- 一個操作中含有 龐大的多分支的條件語句,且這些分支依賴於該對象的狀態。這個狀態通常用一個或多個枚舉常量表示。通常, 有多個操作包含這一相同的條件結構。State模式將每一個條件分支放入一個獨立的類中。這使得你可以根據對象自身的情況將對象的狀態作爲一個對象,這一 對象可以不依賴於其他對象而獨立變化。
3-3-4) 總結
優點:
結構清晰
避免了過多的switch…case或者if…else語句的使用,避免了程序的複雜性,提高系統的可維護性。遵循設計原則
很好地體現了開閉原則和單一職責原則,每個狀態都是一個子類,你要增加狀態就要增加子類,你要修改狀態,你只修改一個子類就可以了。封裝性非常好
這也是狀態模式的基本要求,狀態變換放置到類的內部來實現,外部的調用不用知道類內部如何實現狀態和行爲的變換。
缺點:
子類會太多,類膨脹。注意事項:
狀態模式適用於當某個對象在它的狀態發生改變時,它的行爲也隨着發生比較大的變化,也就是說在行爲受狀態約束的情況下可以使用狀態模式,而且使用時對象的狀態最好不要超過5個。
3-4) Observer 模式
3-4-1) 定義
觀察者模式:定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都將得到通知並被自動更新。
3-4-1) 詳解
Observer 模式典型的結構圖爲:
參與者
Subject(目標)
— 目標知道它的觀察者。可以有任意多個觀察者觀察同一個目標。
— 提供註冊和刪除觀察者對象的接口。 ? Observer(觀察者)
— 爲那些在目標發生改變時需獲得通知的對象定義一個更新接口。ConcreteSubject(具體目標)
— 將有關狀態存入各ConcreteObserver對象。
— 當它的狀態發生改變時 , 向它的各個觀察者發出通知。ConcreteObserver(具體觀察者)
— 維護一個指向ConcreteSubject對象的引用。— 存儲有關狀態,這些狀態應與目標的狀態保持一致。— 實現Observer的更新接口以使自身狀態與目標的狀態保持一致。
3-4-3) 場景
- 當一個抽象模型有兩個方面, 其中一個方面依賴於另一方面。將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和複用。
- 當對一個對象的改變需要同時改變其它對象, 而不知道具體有多少對象有待改變。
- 當一個對象必須通知其它對象,而它又不能假定其它對象是誰。換言之,你不希望這些對象是緊密耦合的。
3-4-4) 總結
Observer 是影響極爲深遠的模式之一,也是在大型系統開發過程中要用到的模式之一。 除了 MFC、Struts 提供了 MVC 的實現框架,在 Java 語言中還提供了專門的接口實現 Observer 模式:通過專門的類 Observable 及 Observer 接口來實現 MVC 編程模式,其 UML 圖可以表 示爲:
這裏的 Observer 就是觀察者,Observable 則充當目標 Subject 的角色。
Observer 模式也稱爲發佈-訂閱(publish-subscribe),目標就是通知的發佈者,觀察者 則是通知的訂閱者(接受通知)。
3-5) Memento 模式
3-5-1) 定義
備忘錄模式(Memento Pattern):在不破壞封裝的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態,這樣可以在以後將對象恢復到原先保存的狀態。它是一種對象行爲型模式,其別名爲Token。
3-5-2) 詳解
Memento 模式的關鍵就是要在不破壞封裝行的前提下,捕獲並保存一個類的內部 狀態,這樣就可以利用該保存的狀態實施恢復操作。爲了達到這個目標,可以在後面的實現 中看到我們採取了一定語言支持的技術。Memento 模式的典型結構圖爲:
Originator(發起人):負責創建一個備忘錄Memento,用以記錄當前時刻自身的內部狀態,並可使用備忘錄恢復內部狀態。Originator可以根據需要決定Memento存儲自己的哪些內部狀態。
Memento(備忘錄):負責存儲Originator對象的內部狀態,並可以防止Originator以外 的其他對象訪問備忘錄。備忘錄有兩個接口:Caretaker只能看到備忘錄的窄接口,他只能將備忘錄傳遞給其他對象。Originator卻可看到備忘 錄的寬接口,允許它訪問返回到先前狀態所需要的所有數據。
Caretaker(管理者):負責備忘錄Memento,不能對Memento的內容進行訪問或者操作。
3-5-3) 場景
- 必須保存一個對象在某一個時刻的(部分)狀態, 這樣以後需要時它才能恢復到先前的狀態。
- 如果一個用接口來讓其它對象直接得到這些狀態,將會暴露對象的實現細節並破壞對象的封裝性
3-5-4) 總結
優點:
有時一些發起人對象的內部信息必須保存在發起人對象以外的地方,但是必須要由發起人對象自己讀取,這時,
使用備忘錄模式可以把複雜的發起人內部信息對其他的對象屏蔽起來,從而可以恰當地保持封裝的邊界。本模式簡化了發起人類。發起人不再需要管理和保存其內部狀態的一個個版本,客戶端可以自行管理他們所需
要的這些狀態的版本。
缺點:
如果發起人角色的狀態需要完整地存儲到備忘錄對象中,那麼在資源消耗上面備忘錄對象會很昂貴。
當負責人角色將一個備忘錄 存儲起來的時候,負責人可能並不知道這個狀態會佔用多大的存儲空間,從而無法提醒用戶一個操作是否很昂貴。
當發起人角色的狀態改變的時候,有可能這個協議無效。如果狀態改變的成功率不高的話,不如採取“假如”協議模式。
3-6) Mediator 模式
3-6-1) 定義
使用一箇中介的對象,封裝一組對象之間的交互,這樣這些對象就可以不用彼此耦合。
這個中介者常常起着中間橋樑的作用,使其他的對象可以利用中介者完成某些行爲活動,因此它必須對所有的參與活動的對象瞭如指掌!
3-6-2) 詳解
Mediator 模式典型的結構圖爲:
Mediator(中介者)
中介者定義一個接口用於與各同事( Colleague)對象通信。ConcreteMediator(具體中介者)
具體中介者通過協調各同事對象實現協作行爲。
瞭解並維護它的各個同事。Colleague class(同事類)
每一個同事類都知道它的中介者對象。
每一個同事對象在需與其他的同事通信的時候,與它的中介者通信。
3-6-3) 場景
- 一組對象以定義良好但是複雜的方式進行通信。產生的相互依賴關係結構混亂且難以理解。
- 一個對象引用其他很多對象並且直接與這些對象通信,導致難以複用該對象。
- 想定製一個分佈在多個類中的行爲,而又不想生成太多的子類。
3-6-4) 總結
優點:
中介者模式的優點就是減少類間的依賴,把原有的一對多的依賴變成了一對一的依賴,同事類只依賴中介者,減少了依賴,當然同時也降低了類間的耦合。缺點:
中介者模式的缺點就是中介者會膨脹得很大,而且邏輯複雜,原本N個對象直接的相互依賴關係轉換爲中介者和同事類的依賴關係,同事類越多,中介者的邏輯就越複雜。
3-7) Command 模式
3-7-1) 定義
將一個請求封裝爲一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支持可撤銷的操作。
3-7-1) 詳解
Command 模式的典型結構圖爲:
* Command:
定義命令的接口,聲明執行的方法。
ConcreteCommand:
命令接口實現對象,是“虛”的實現;通常會持有接收者,並調用接收者的功能來完成命令要執行的操作。Receiver:
接收者,真正執行命令的對象。任何類都可能成爲一個接收者,只要它能夠實現命令要求實現的相應功能。Invoker:
要求命令對象執行請求,通常會持有命令對象,可以持有很多的命令對象。這個是客戶端真正觸發命令並要求命令執行相應操作的地方,也就是說相當於使用命令對象的入口。Client:
創建具體的命令對象,並且設置命令對象的接收者。注意這個不是我們常規意義上的客戶端,而是在組裝命令對象和接收者,或許,把這個Client稱爲裝配者會更好理解,因爲真正使用命令的客戶端是從Invoker來觸發執行。
3-7-3) 場景
- 系統需要將請求調用者和請求接收者解耦,使得調用者和接收者不直接交互。
- 系統需要在不同的時間指定請求、將請求排隊和執行請求。
- 系統需要支持命令的撤銷(Undo)操作和恢復(Redo)操作。
- 系統需要將一組操作組合在一起,即支持宏命令。
3-7-4) 總結
優點:
- 降低系統的耦合度
- 新的命令可以很容易地加入到系統中
- 可以比較容易地設計一個組合命令
- 調用同一方法實現不同的功能
缺點:
- 使用命令模式可能會導致某些系統有過多的具體命令類。因爲針對每一個命令都需要設計一個具體命令類,因此某些系統可能需要大量具體命令類,這將影響命令模式的使用。
3-8) Visitor 模式
3-8-1) 定義
表示一個作用於某對象結構中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。
3-8-2) 詳解
我們通過 Visitor 模式解決上面的問題,其典型的結構圖爲:
Visitor 模式在不破壞類的前提下,爲類提供增加新的新操作。Visitor 模式的關鍵是雙分
派(Double-Dispatch)的技術【註釋 1】。C++語言支持的是單分派。
在 Visitor 模式中 Accept()操作是一個雙分派的操作。具體調用哪一個具體的 Accept
()操作,有兩個決定因素:1)Element 的類型。因爲 Accept()是多態的操作,需要具體的 Element 類型的子類纔可以決定到底調用哪一個 Accept()實現;2)Visitor 的類型。 Accept()操作有一個參數(Visitor* vis),要決定了實際傳進來的Visitor的實際類別纔可 以決定具體是調用哪個 VisitConcrete()實現。
3-8-3) 場景
一個對象結構包含很多類對象,它們有不同的接口,而你想對這些對象實施一些依賴於其具體類的操作。
需要對一個對象結構中的對象進行很多不同的並且不相關的操作,而你想避免讓這些操作“污染”這些對象的類。Visitor使得你可以將相關的操作集中起來定義在一個類中。當該對象結構被很多應用共享時,用Visitor模式讓每個應用僅包含需要用到的操作。
定義對象結構的類很少改變,但經常需要在此結構上定義新的操作。改變對象結構類需要重定義對所有訪問者的接口,這可能需要很大的代價。如果對象結構類經常改變,那麼可能還是在這些類中定義這些操作較好。
3-8-4) 總結
在這種情境下你一定要考慮使用訪問者模式:
- 業務規則要求遍歷多個不同的對象。這本身也是訪問者模式出發點,迭代器模式只能訪問同類或同接口的數據(當然了,如果你使用instanceof,那麼能訪問所有的數據,這沒有爭論),而訪問者模式是對迭代器模式的擴充,可以遍歷不同的對象,然後執行不同的操作,也就是針對訪問的對象不同,執行不同的操作。
Visitor 模式看似比較複雜,簡單概括就是:
- Visitor 是對數據處理算法的封裝
- Element 是對數據結構的封裝
- 本來可以單項依賴,就是visitor.visit(xxxElement), 但是爲了減少Visitor中if的判斷,把所有的if拆分到到Element中自己確認調用方法也就是在accept(vistor)方法中執行visitor.visit(xxxElement),所有就形成了雙向依賴Element->Visitor
優點:
- 數據(Element)和算法(Visitor)分離
- 便於算法(Visitor)的添加,修改
缺點:
- 數據(Element)添加比較麻煩,會影響到Visitor,需要在每個Visitor中添加visitXXXElement方法
3-9) Chain of Responsibility 模式
3-9-1) 定義
使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係。將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止。
3-9-2) 詳解
Chain of Responsibility 模式典型的結構圖爲:
Handler
定義一個處理請求的接口。
(可選) 實現後繼鏈。ConcreteHandler
處理它所負責的請求。
可訪問它的後繼者。
如果可處理該請求,就處理之;否則將該請求轉發給它的後繼者。Client
向鏈上的具體處理者(ConcreteHandler)對象提交請求。
3-9-3) 場景
- 有多個的對象可以處理一個請求,哪個對象處理該請求運行時刻自動確定。
- 你想在不明確指定接收者的情況下,向多個對象中的一個提交一個請求。
- 可處理一個請求的對象集合應被動態指定。
3-9-4) 總結
Chain of Responsibility 模式的最大的一個優點就是給系統降低了耦合性,請求的發送者
完全不必知道該請求會被哪個應答對象處理,極大地降低了系統的耦合性。
3-10) Iterator 模式
3-10-1) 定義
Iterator 模式也正是用來解決對一個聚合對象的遍歷問題,將對聚合的遍歷封裝到一個 類中進行,這樣就避免了暴露這個聚合對象的內部表示的可能。
3-10-2) 詳解
Iterator 模式典型的結構圖爲:
Iterator(迭代器)
迭代器定義訪問和遍歷元素的接口。ConcreteIterator (具體迭代器)
具體迭代器實現迭代器接口。
對該聚合遍歷時跟蹤當前位置。Aggregate (聚合)
聚合定義創建相應迭代器對象的接口。ConcreteAggregate (具體聚合)
具體聚合實現創建相應迭代器的接口,該操作返回ConcreteIterator的一個適當的實例。
3-10-3) 場景
- 訪問一個聚合對象的內容而無需暴露它的內部表示。
- 支持對聚合對象的多種遍歷。
- 爲遍歷不同的聚合結構提供一個統一的接口(即, 支持多態迭代)。
3-10-4) 總結
Iterator 模式的應用很常見,我們在開發中就經常會用到 STL 中預定義好的 Iterator 來對 STL 類進行遍歷(Vector、Set 等)
3-11) Interpreter 模式
3-11-1) 定義
給定一門語言,定義它的文法的一種表示,並定義一個解釋器,該解釋器使用該表示來解釋語言中句子。
3-11-2) 詳解
Interpreter 模式典型的結構圖爲:
模式所涉及的角色如下所示:
- 抽象表達式(Expression)角色:聲明一個所有的具體表達式角色都需要實現的抽象接口。這個接口主要是一個interpret()方法,稱做解釋操作。
- 終結符表達式(Terminal Expression)角色:實現了抽象表達式角色所要求的接口,主要是一個interpret()方法;文法中的每一個終結符都有一個具體終結表達式與之相對應。比如有一個簡單的公式R=R1+R2,在裏面R1和R2就是終結符,對應的解析R1和R2的解釋器就是終結符表達式。
- 非終結符表達式(Nonterminal Expression)角色:文法中的每一條規則都需要一個具體的非終結符表達式,非終結符表達式一般是文法中的運算符或者其他關鍵字,比如公式R=R1+R2中,“+”就是非終結符,解析“+”的解釋器就是一個非終結符表達式。
- 環境(Context)角色:這個角色的任務一般是用來存放文法中各個終結符所對應的具體值,比如R=R1+R2,我們給R1賦值100,給R2賦值200。這些信息需要存放到環境角色中,很多情況下我們使用Map來充當環境角色就足夠了。
3-11-3) 適用性
當有一個語言需要解釋執行 , 並且你可將該語言中的句子表示爲一個抽象語法樹時,可使用解釋器模式。而當存在以下情況時該模式效果最好:
該文法簡單對於複雜的文法 , 文法的類層次變得龐大而無法管理。此時語法分析程序生成器這樣的工具是更好的選擇。它們無需構建抽象語法樹即可解釋表達式 , 這樣可以節 省空間而且還可能節省時間。
效率不是一個關鍵問題最高效的解釋器通常不是通過直接解釋語法分析樹實現的 , 而是首先將它們轉換成另一種形式。例如,正則表達式通常被轉換成狀態機。但即使在這種 情況下, 轉換器仍可用解釋器模式實現, 該模式仍是有用的。
3-11-4) 總結
Interpreter 模式則提供了一種很好的組織和設計這種解析器的架構。
Interpreter 模式中使用類來表示文法規則,因此可以很容易實現文法的擴展。另外對於 終結符我們可以使用 Flyweight 模式來實現終結符的共享。
優點:
- 易於改變和擴展文法。由於在解釋器模式中使用類來表示語言的文法規則,因此可以通過繼承等機制來改變或擴展文法。
- 每一條文法規則都可以表示爲一個類,因此可以方便地實現一個簡單的語言。
- 實現文法較爲容易。在抽象語法樹中每一個表達式節點類的實現方式都是相似的,這些類的代碼編寫都不會特別複雜,還可以通過一些工具自動生成節點類代碼。
- 增加新的解釋表達式較爲方便。如果用戶需要增加新的解釋表達式只需要對應增加一個新的終結符表達式或非終結符表達式類,原有表達式類代碼無須修改,符合“開閉原則”。
缺點:
- 對於複雜文法難以維護。在解釋器模式中,每一條規則至少需要定義一個類,因此如果一個語言包含太多文法規則,類的個數將會急劇增加,導致系統難以管理和維護,此時可以考慮使用語法分析程序等方式來取代解釋器模式。
- 執行效率較低。由於在解釋器模式中使用了大量的循環和遞歸調用,因此在解釋較爲複雜的句子時其速度很慢,而且代碼的調試過程也比較麻煩。