0.1 設計模式解析(總序)
0.2 設計模式解析後記
0.3 與作者聯繫
1 創建型模式
1.1 Factory模式
1)定義創建對象的接口,封裝了對象的創建;
2)使得具體化類的工作延遲到了子類中。
1)如果爲每一個具體的 ConcreteProduct 類的實例化提供一個函數體,那麼我們可能不得不在系統中添加了一個方法來處理這個新建的 ConcreteProduct,這樣 Factory 的接口永遠就不肯能封閉(Close) 。當然我們可以通過創建一個 Factory的子類來通過多態實現這一點,但是這也是以新建一個類作爲代價的。
2)在實現中我們可以通過參數化工廠方法,即給 FactoryMethod()傳遞一個參數用以決定是創建具體哪一個具體的 Product(實際上筆者在 VisualCMCS 中也正是這樣做的) 。當然也可以通過模板化避免 1)中的子類創建子類,其方法就是將具體 Product 類作爲模板參數,實現起來也很簡單。
可以看出,Factory 模式對於對象的創建給予開發人員提供了很好的實現策略,但是Factory 模式僅僅侷限於一類類(就是說 Product 是一類,有一個共同的基類) ,如果我們要爲不同類的類提供一個對象創建的接口,那就要用abstractFactory了。
1.2 AbstactFactory模式
(ConcreteFactory) 中, 維護這樣一個創建類總比維護 n 多相關對象的創建過程要簡單的多。
1.3 Singleton模式
Singleton 模式解決問題十分常見,我們怎樣去創建一個唯一的變量(對象)?在基於對象的設計中我們可以通過創建一個全局變量(對象)來實現,在面向對象和麪向過程結合的設計範式(如 C++中)中,我們也還是可以通過一個全局變量實現這一點。但是當我們遇到了純粹的面向對象範式中,這一點可能就只能是通過 Singleton 模式來實現了,可能這也正是很多公司在招聘 Java 開發人員時候經常考察 Singleton 模式的緣故吧。 Singleton 模式在開發中非常有用,具體使用在討論給出。
Singleton 模式經常和 Factory(AbstractFactory)模式在一起使用,因爲系統中工廠對象一般來說只要一個,筆者在開發 Visual CMCS 的時候,語義分析過程(以及其他過程)中都用到工廠模式來創建對象(對象實在是太多了) ,這裏的工廠對象實現就是同時是一個Singleton 模式的實例,因爲系統我們就只要一個工廠來創建對象就可以了。
{
Singleton* sgn = Singleton::Instance();
return 0;
}
1.4 Builder模式
Builder 模式要解決的也正是這樣的問題:當我們要創建的對象很複雜的時候(通常是由很多其他的對象組合而成) ,我們要要複雜對象的創建過程和這個對象的表示(展示)分離開來,這樣做的好處就是通過一步步的進行復雜對象的構建,由於在每一步的構造過程中可以引入參數,使得經過相同的步驟創建最後得到的對象的展示不一樣。
Builder模式和AbstractFactory模式在功能上很相似,因爲都是用來創建大的複雜的對象,它們的區別是:Builder模式強調的是一步步創建對象,並通過相同的創建過程可以獲得不同的結果對象,一般來說Builder模式中對象不是直接返回的。而在AbstractFactory模式中對象是直接返回的,AbstractFactory模式強調的是爲創建多個相互依賴的對象提供一個同一的接口。
Prototype模式的結構和實現都很簡單,其關鍵就是(C++中)拷貝構造函數的實現方式,這也是C++實現技術層面上的事情。由於在示例代碼中不涉及到深層拷貝(主要指有指針、複合對象的情況),因此我們通過編譯器提供的默認的拷貝構造函數(按位拷貝)的方式進行實現。說明的是這一切只是爲了實現簡單起見,也因爲本文檔的重點不在拷貝構造函數的實現技術,而在Prototype模式本身的思想。
2.1 Bridge模式
1)客戶給了你一個需求,於是使用一個類來實現(A);
2)客戶需求變化,有兩個算法實現功能,於是改變設計,我們通過一個抽象的基類,再定義兩個具體類實現兩個不同的算法(A1和A2);
3)客戶又告訴我們說對於不同的操作系統,於是再抽象一個層次,作爲一個抽象基類A0,在分別爲每個操作系統派生具體類(A00和A01,其中A00表示原來的類A)實現不同操作系統上的客戶需求,這樣我們就有了一共4個類。
4)可能用戶的需求又有變化,比如說又有了一種新的算法……..
5)我們陷入了一個需求變化的鬱悶當中,也因此帶來了類的迅速膨脹。
Bridge模式則正是解決了這類問題。
在Bridge模式的結構圖中可以看到,系統被分爲兩個相對獨立的部分,左邊是抽象部分,右邊是實現部分,這兩個部分可以互相獨立地進行修改:例如上面問題中的客戶需求變化,當用戶需求需要從Abstraction派生一個具體子類時候,並不需要像上面通過繼承方式實現時候需要添加子類A1和A2了。另外當上面問題中由於算法添加也只用改變右邊實現(添加一個具體化子類),而右邊不用在變化,也不用添加具體子類了。
GoF在說明Bridge模式時,在意圖中指出Bridge模式“將抽象部分與它的實現部分分離,使得它們可以獨立地變化”。這句話很簡單,但是也很複雜,連Bruce Eckel在他的大作《Thinking in Patterns》中說“Bridge模式是GoF所講述得最不好(Poorly-described)的模式”,個人覺得也正是如此。原因就在於GoF的那句話中的“實現”該怎麼去理解:“實現”特別是和“抽象”放在一起的時候我們“默認”的理解是“實現”就是“抽象”的具體子類的實現,但是這裏GoF所謂的“實現”的含義不是指抽象基類的具體子類對抽象基類中虛函數(接口)的實現,是和繼承結合在一起的。而這裏的“實現”的含義指的是怎麼去實現用戶的需求,並且指的是通過組合(委託)的方式實現的,因此這裏的實現不是指的繼承基類、實現基類接口,而是指的是通過對象組合實現用戶的需求。理解了這一點也就理解了Bridge模式,理解了Bridge模式,你的設計就會更加Elegant了。
實際上上面使用Bridge模式和使用帶來問題方式的解決方案的根本區別在於是通過繼承還是通過組合的方式去實現一個功能需求。因此面向對象分析和設計中有一個原則就是:Favor Composition Over Inheritance。其原因也正在這裏。
在上面生活中問題的解決方式也就正好對應了Adapter模式的兩種類別:類模式和對象模式。
Adapter模式實現上比較簡單,要說明的是在類模式Adapter中,我們通過private繼承Adaptee獲得實現繼承的效果,而通過public繼承Target獲得接口繼承的效果(有關實現繼承和接口繼承參見討論部分)。
而Decorator提供了一種給類增加職責的方法,不是通過繼承實現的,而是通過組合。
在結構圖中,ConcreteComponent和Decorator需要有同樣的接口,因此ConcreteComponent和Decorator有着一個共同的父類。這裏有人會問,讓Decorator直接維護一個指向ConcreteComponent引用(指針)不就可以達到同樣的效果,答案是肯定並且是否定的。肯定的是你可以通過這種方式實現,否定的是你不要用這種方式實現,因爲通過這種方式你就只能爲這個特定的ConcreteComponent提供修飾操作了,當有了一個新的ConcreteComponent你又要去新建一個Decorator來實現。但是通過結構圖中的ConcreteComponent和Decorator有一個公共基類,就可以利用OO中多態的思想來實現只要是Component型別的對象都可以提供修飾操作的類,這種情況下你就算新建了100個Component型別的類ConcreteComponent,也都可以由Decorator一個類搞定。這也正是Decorator模式的關鍵和威力所在了。
當然如果你只用給Component型別類添加一種修飾,則Decorator這個基類就不是很必要了。
Decorator模式和Proxy模式的相似的地方在於它們都擁有一個指向其他對象的引用(指針),即通過組合的方式來爲對象提供更多操作(或者Decorator模式)間接性(Proxy模式)。
Decorator模式除了採用組合的方式取得了比採用繼承方式更好的效果,Decorator模式還給設計帶來一種“即用即付”的方式來添加職責。在OO設計和分析經常有這樣一種情況:爲了多態,通過父類指針指向其具體子類,但是這就帶來另外一個問題,當具體子類要添加新的職責,就必須向其父類添加一個這個職責的抽象接口,否則是通過父類指針是調用不到這個方法了。這樣處於高層的父類就承載了太多的特徵(方法),並且繼承自這個父類的所有子類都不可避免繼承了父類的這些接口,但是可能這並不是這個具體子類所需要的。而在Decorator模式提供了一種較好的解決方法,當需要添加一個操作的時候就可以通過Decorator模式來解決,你可以一步步添加新的職責。
Composite模式在實現中有一個問題就是要提供對於子節點(Leaf)的管理策略,這裏使用的是STL 中的vector,可以提供其他的實現方式,如數組、鏈表、Hash表等.
可以從圖2-1中看出,Flyweight模式中有一個類似Factory模式的對象構造工廠FlyweightFactory,當客戶程序員(Client)需要一個對象時候就會向FlyweightFactory發出請求對象的消息GetFlyweight()消息,FlyweightFactory擁有一個管理、存儲對象的“倉庫”(或者叫對象池,vector實現),GetFlyweight()消息會遍歷對象池中的對象,如果已經存在則直接返回給Client,否則創建一個新的對象返回給Client。當然可能也有不想被共享的對象(例如結構圖中的UnshareConcreteFlyweight),但不在本模式的講解範圍,故在實現中不給出。
這裏的客戶程序員就是上面生活中想辦理手續的鬱悶的人!在現實生活中我們可能可以很快想到找一個人代理所有的事情就可以解決你的問題(你只要維護和他的簡單的一個接口而已了!),在軟件系統設計開發中我們可以通過一個叫做Façade的模式來解決上面的問題。
2.7 Proxy模式
1)創建開銷大的對象時候,比如顯示一幅大的圖片,我們將這個創建的過程交給代理去完成,GoF稱之爲虛代理(Virtual Proxy);
2)爲網絡上的對象創建一個局部的本地代理,比如要操作一個網絡上的一個對象(網絡性能不好的時候,問題尤其突出),我們將這個操縱的過程交給一個代理去完成,GoF稱之爲遠程代理(Remote Proxy);
3)對對象進行控制訪問的時候,比如在Jive論壇中不同權限的用戶(如管理員、普通用戶等)將獲得不同層次的操作權限,我們將這個工作交給一個代理去完成,GoF稱之爲保護代理(Protection Proxy)。
4)智能指針(Smart Pointer),關於這個方面的內容,建議參看Andrew Koenig的《C++沉思錄》中的第5章。
Proxy模式最大的好處就是實現了邏輯和實現的徹底解耦。
3.1 Template模式
Template模式是採用繼承的方式實現這一點:將邏輯(算法)框架放在抽象基類中,並定義好細節的接口,子類中實現細節。【註釋1】
【註釋1】:Strategy模式解決的是和Template模式類似的問題,但是Strategy模式是將邏輯(算法)封裝到一個類中,並採取組合(委託)的方式解決這個問題。
3.2 Strategy模式
Template模式實際上就是利用面向對象中多態的概念實現算法實現細節和高層接口的鬆耦合。可以看到Template模式採取的是繼承方式實現這一點的,由於繼承是一種強約束性的條件,因此也給Template模式帶來一些許多不方便的地方(有關這一點將在討論中展開)。
唯一注意的是我們將原語操作(細節算法)定義未保護(Protected)成員,只供模板方法調用(子類可以)。
Template模式獲得一種反向控制結構效果,這也是面向對象系統的分析和設計中一個原則DIP(依賴倒置:Dependency Inversion Principles)。其含義就是父類調用子類的操作(高層模塊調用低層模塊的操作),低層模塊實現高層模塊聲明的接口。這樣控制權在父類(高層模塊),低層模塊反而要依賴高層模塊。
繼承的強制性約束關係也讓Template模式有不足的地方,我們可以看到對於ConcreteClass類中的實現的原語方法Primitive1(),是不能被別的類複用。假設我們要創建一個AbstractClass的變體AnotherAbstractClass,並且兩者只是通用算法不一樣,其原語操作想複用AbstractClass的子類的實現。但是這是不可能實現的,因爲ConcreteClass繼承自AbstractClass,也就繼承了AbstractClass的通用算法,AnotherAbstractClass是復用不了ConcreteClass的實現,因爲後者不是繼承自前者。
Template模式暴露的問題也正是繼承所固有的問題,Strategy模式則通過組合(委託)來達到和Template模式類似的效果,其代價就是空間和時間上的代價,關於Strategy模式的詳細討論請參考Strategy模式解析。
這裏的關鍵就是將算法的邏輯抽象接口(DoAction)封裝到一個類中(Context),再通過委託的方式將具體的算法實現委託給具體的Strategy類來實現(ConcreteStrategeA類)。
1) 繼承:
優點
1)易於修改和擴展那些被複用的實現。
缺點
1)破壞了封裝性,繼承中父類的實現細節暴露給子類了;
2)“白盒”複用,原因在1)中;
3)當父類的實現更改時,其所有子類將不得不隨之改變
4)從父類繼承而來的實現在運行期間不能改變(編譯期間就已經確定了)。
優點
1)“黑盒”複用,因爲被包含對象的內部細節對外是不可見的;
2)封裝性好,原因爲1);
3)實現和抽象的依賴性很小(組合對象和被組合對象之間的依賴性小);
4)可以在運行期間動態定義實現(通過一個指向相同類型的指針,典型的是抽象基類的指針)。
缺點
1)系統中對象過多。
從上面對比中我們可以看出,組合相比繼承可以取得更好的效果,因此在面向對象的設計中的有一條很重要的原則就是:優先使用(對象)組合,而非(類)繼承(Favor Composition Over Inheritance)。
實際上,繼承是一種強制性很強的方式,因此也使得基類和具體子類之間的耦合性很強。例如在Template模式中在ConcreteClass1中定義的原語操作別的類是不能夠直接複用(除非你繼承自AbstractClass,具體分析請參看Template模式文檔)。而組合(委託)的方式則有很小的耦合性,實現(具體實現)和接口(抽象接口)之間的依賴性很小,例如在本實現中,ConcreteStrategyA的具體實現操作很容易被別的類複用,例如我們要定義另一個Context類AnotherContext,只要組合一個指向Strategy的指針就可以很容易地複用ConcreteStrategyA的實現了。
我們在Bridge模式的問題和Bridge模式的分析中,正是說明了繼承和組合之間的區別。請參看相應模式解析。
另外Strategy模式很State模式也有相似之處,但是State模式注重的對象在不同的狀態下不同的操作。兩者之間的區別就是State模式中具體實現類中有一個指向Context的引用,而Strategy模式則沒有。具體分析請參看相應的State模式分析中。
有限狀態自動機(FSM)也是一個典型的狀態不同,對輸入有不同的響應(狀態轉移)。通常我們在實現這類系統會使用到很多的Switch/Case語句,Case某種狀態,發生什麼動作,Case另外一種狀態,則發生另外一種狀態。但是這種實現方式至少有以下兩個問題:
1)當狀態數目不是很多的時候,Switch/Case可能可以搞定。但是當狀態數目很多的時候(實際系統中也正是如此),維護一大組的Switch/Case語句將是一件異常困難並且容易出錯的事情。
2)狀態邏輯和動作實現沒有分離。在很多的系統實現中,動作的實現代碼直接寫在狀態的邏輯當中。這帶來的後果就是系統的擴展性和維護得不到保證。
1)將State聲明爲Context的友元類(friend class),其作用是讓State模式訪問Context的protected接口ChangeSate()。
2)State及其子類中的操作都將Context*傳入作爲參數,其主要目的是State類可以通過這個指針調用Context中的方法(在本示例代碼中沒有體現)。這也是State模式和Strategy模式的最大區別所在。
運行了示例代碼後可以獲得以下的結果:連續3次調用了Context的OprationInterface()因爲每次調用後狀態都會改變(A-B-A),因此該動作隨着Context的狀態的轉變而獲得了不同的結果。
State模式和Strategy模式又很大程度上的相似:它們都有一個Context類,都是通過委託(組合)給一個具有多個派生類的多態基類實現Context的算法邏輯。兩者最大的差別就是State模式中派生類持有指向Context對象的引用,並通過這個引用調用Context中的方法,但在Strategy模式中就沒有這種情況。因此可以說一個State實例同樣是Strategy模式的一個實例,反之卻不成立。實際上State模式和Strategy模式的區別還在於它們所關注的點不盡相同:State模式主要是要適應對象對於狀態改變時的不同處理策略的實現,而Strategy則主要是具體算法和實現接口的解耦(coupling),Strategy模式中並沒有狀態的概念(雖然很多時候有可以被看作是狀態的概念),並且更加不關心狀態的改變了。
State模式很好地實現了對象的狀態邏輯和動作實現的分離,狀態邏輯分佈在State的派生類中實現,而動作實現則可以放在Context類中實現(這也是爲什麼State派生類需要擁有一個指向Context的指針)。這使得兩者的變化相互獨立,改變State的狀態邏輯可以很容易複用Context的動作,也可以在不影響State派生類的前提下創建Context的子類來更改或替換動作實現。
State模式問題主要是邏輯分散化,狀態邏輯分佈到了很多的State的子類中,很難看到整個的狀態邏輯圖,這也帶來了代碼的維護問題。
當然,MVC只是Observer模式的一個實例。Observer模式要解決的問題爲:建立一個一(Subject)對多(Observer)的依賴關係,並且做到當“一”變化的時候,依賴這個“一”的多也能夠同步改變。最常見的一個例子就是:對同一組數據進行統計分析時候,我們希望能夠提供多種形式的表示(例如以表格進行統計顯示、柱狀圖統計顯示、百分比統計顯示等)。這些表示都依賴於同一組數據,我們當然需要當數據改變的時候,所有的統計的顯示都能夠同時改變。Observer模式就是解決了這一個問題。
這裏的目標Subject提供依賴於它的觀察者Observer的註冊(Attach)和註銷(Detach)操作,並且提供了使得依賴於它的所有觀察者同步的操作(Notify)。觀察者Observer則提供一個Update操作,注意這裏的Observer的Update操作並不在Observer改變了Subject目標狀態的時候就對自己進行更新,這個更新操作要延遲到Subject對象發出Notify通知所有Observer進行修改(調用Update)。
調用Notify操作就遍歷list中的Observer對象,並廣播通知改變狀態(調用Observer的Update操作)。目標的狀態state可以由Subject自己改變(示例),也可以由Observer的某個操作引起state的改變(可調用Subject的SetState操作)。Notify操作可以由Subject目標主動廣播(示例),也可以由Observer觀察者來調用(因爲Observer維護一個指向Subject的指針)。
運行示例程序,可以看到當Subject處於狀態“old”時候,依賴於它的兩個觀察者都顯示“old”,當目標狀態改變爲“new”的時候,依賴於它的兩個觀察者也都改變爲“new”Observer是影響極爲深遠的模式之一,也是在大型系統開發過程中要用到的模式之一。除了MFC、Struts提供了MVC的實現框架,在Java語言中還提供了專門的接口實現Observer模式:通過專門的類Observable及Observer接口來實現MVC編程模式,其UML圖可以表示爲:
Observer模式也稱爲發佈-訂閱(publish-subscribe),目標就是通知的發佈者,觀察者則是通知的訂閱者(接受通知)。
Memento模式的關鍵就是friend class Originator;我們可以看到,Memento的接口都聲明爲private,而將Originator聲明爲Memento的友元類。我們將Originator的狀態保存在Memento類中,而將Memento接口private起來,也就達到了封裝的功效。
在Originator類中我們提供了方法讓用戶後悔:RestoreToMemento(Memento* mt);我們可以通過這個接口讓用戶後悔。在測試程序中,我們演示了這一點:Originator的狀態由old變爲new最後又回到了old。
討論
在Command模式中,Memento模式經常被用來維護可以撤銷(Undo)操作的狀態。這一點將在Command模式具體說明。
Mediator模式中,每個Colleague維護一個Mediator,當要進行交互,例如圖中ConcreteColleagueA和ConcreteColleagueB之間的交互就可以通過ConcreteMediator提供的DoActionFromAtoB來處理,ConcreteColleagueA和ConcreteColleagueB不必維護對各自的引用,甚至它們也不知道各個的存在。Mediator通過這種方式將多對多的通信簡化爲了一(Mediator)對多(Colleague)的通信。
1)將ConcreteColleageA對象設置狀態“old”,ConcreteColleageB也設置狀態“old”;
2)ConcreteColleageA對象改變狀態,並在Action中和ConcreteColleageB對象進行通信,並改變ConcreteColleageB對象的狀態爲“new”;
3)ConcreteColleageB對象改變狀態,並在Action中和ConcreteColleageA對象進行通信,並改變ConcreteColleageA對象的狀態爲“new”;
注意到,兩個Colleague對象並不知道它交互的對象,並且也不是顯示地處理交互過程,這一切都是通過Mediator對象完成的,示例程序運行的結果也正是證明了這一點。
Mediator模式還有一個很顯著額特點就是將控制集中,集中的優點就是便於管理,也正式符合了OO設計中的每個類的職責要單一和集中的原則。
Command模式結構圖中,將請求的接收者(處理者)放到Command的具體子類ConcreteCommand中,當請求到來時(Invoker發出Invoke消息激活Command對象),ConcreteCommand將處理請求交給Receiver對象進行處理。
但是世界上的事情不是絕對的,上面提到的方式在OO設計種絕大部分的時候可能是一個不成熟的體現,但是在Command模式中卻是起到了很好的效果。主要體現在:
1) Command模式將調用操作的對象和知道如何實現該操作的對象解耦。在上面Command的結構圖中,Invoker對象根本就不知道具體的是那個對象在處理Excute操作(當然要知道是Command類別的對象,也僅此而已)。
2) 在Command要增加新的處理操作對象很容易,我們可以通過創建新的繼承自Command的子類來實現這一點。
3) Command模式可以和Memento模式結合起來,支持取消的操作。
Visitor模式則提供了一種解決方案:將更新(變更)封裝到一個類中(訪問操作),並由待更改類提供一個接收接口,則可達到效果。
Visitor模式在不破壞類的前提下,爲類提供增加新的新操作。Visitor模式的關鍵是雙分派(Double-Dispatch)的技術【註釋1】。C++語言支持的是單分派。
在Visitor模式中Accept()操作是一個雙分派的操作。具體調用哪一個具體的Accept()操作,有兩個決定因素:1)Element的類型。因爲Accept()是多態的操作,需要具體的Element類型的子類纔可以決定到底調用哪一個Accept()實現;2)Visitor的類型。Accept()操作有一個參數(Visitor* vis),要決定了實際傳進來的Visitor的實際類別纔可以決定具體是調用哪個VisitConcrete()實現。
1)Visitor類中的Visit()操作的實現。
這裏我們可以向Element類僅僅提供一個接口Visit(),而在Accept()實現中具體調用哪一個Visit()操作則通過函數重載(overload)的方式實現:我們提供Visit()的兩個重載版本a)Visit(ConcreteElementA* elmA),b)Visit(ConcreteElementB* elmB)。
在C++中我們還可以通過RTTI(運行時類型識別:Runtime type identification)來實現,即我們只提供一個Visit()函數體,傳入的參數爲Element*型別參數 ,然後用RTTI決定具體是哪一類的ConcreteElement參數,再決定具體要對哪個具體類施加什麼樣的具體操作。【註釋2】RTTI給接口帶來了簡單一致性,但是付出的代價是時間(RTTI的實現)和代碼的Hard編碼(要進行強制轉換)。
Visitor模式可以使得Element在不修改自己的同時增加新的操作,但是這也帶來了至少以下的兩個顯著問題:
1) 破壞了封裝性。Visitor模式要求Visitor可以從外部修改Element對象的狀態,這一般通過兩個方式來實現:a)Element提供足夠的public接口,使得Visitor可以通過調用這些接口達到修改Element狀態的目的;b)Element暴露更多的細節給Visitor,或者讓Element提供public的實現給Visitor(當然也給了系統中其他的對象),或者將Visitor聲明爲Element的friend類,僅將細節暴露給Visitor。但是無論那種情況,特別是後者都將是破壞了封裝性原則(實際上就是C++的friend機制得到了很多的面向對象專家的詬病)。
2) ConcreteElement的擴展很困難:每增加一個Element的子類,就要修改Visitor的
接口,使得可以提供給這個新增加的子類的訪問機制。從上面我們可以看到,或者增加一個用於處理新增類的Visit()接口,或者重載一個處理新增類的Visit()操作,或者要修改RTTI方式實現的Visit()實現。無論那種方式都給擴展新的Element子類帶來了困難。
1) MDI主窗口(CMDIFrameWnd)收到命令消息WM_COMMAND,其ID位ID_×××;
2) MDI主窗口將消息傳給當前活動的MDI子窗口(CMDIChildWnd);
3) MDI子窗口給自己的子窗口(View)一個處理機會,將消息交給View;
4) View檢查自己Message Map;
5) 如果View沒有發現處理該消息的程序,則將該消息傳給其對應的Document對象;否則View處理,消息流程結束。
6) Document檢查自己Message Map,如果沒有該消息的處理程序,則將該消息傳給其對象的DocumentTemplate處理;否則自己處理,消息流程結束;
7) 如果在6)中消息沒有得到處理,則將消息返回給View;
8) View再傳回給MDI子窗口;
9) MDI子窗口將該消息傳給CwinApp對象,CwinApp爲所有無主的消息提供了處理。
註明:有關MFC消息處理更加詳細信息,請參考候捷先生的《深入淺出MFC》。
MFC提供了消息的處理的鏈式處理策略,處理消息的請求將沿着預先定義好的路徑依
次進行處理。消息的發送者並不知道該消息最後是由那個具體對象處理的,當然它也無須也不想知道,但是結構是該消息被某個對象處理了,或者一直到一個終極的對象進行處理了。
Chain of Responsibility模式描述其實就是這樣一類問題將可能處理一個請求的對象鏈接成一個鏈,並將請求在這個鏈上傳遞,直到有對象處理該請求(可能需要提供一個默認處理所有請求的類,例如MFC中的CwinApp類)。
Chain of Responsibility模式中ConcreteHandler將自己的後繼對象(向下傳遞消息的對象)記錄在自己的後繼表中,當一個請求到來時,ConcreteHandler會先檢查看自己有沒有匹配的處理程序,如果有就自己處理,否則傳遞給它的後繼。當然這裏示例程序中爲了簡化,ConcreteHandler只是簡單的檢查看自己有沒有後繼,有的話將請求傳遞給後繼進行處理,沒有的話就自己處理。
ConcreteHandleA的對象和h1擁有一個後繼ConcreteHandleB的對象h2,當一個請求到來時候,h1檢查看自己有後繼,於是h1直接將請求傳遞給其後繼h2進行處理,h2因爲沒有後繼,當請求到來時候,就只有自己提供響應了。於是程序的輸出爲:
1) ConcreteHandleA 我把處理權給後繼節點.....;
2) ConcreteHandleB 沒有後繼了,我必須自己處理....。
Chain of Responsibility模式的最大的一個有點就是給系統降低了耦合性,請求的發送者完全不必知道該請求會被哪個應答對象處理,極大地降低了系統的耦合性。
Iterator模式也正是用來解決對一個聚合對象的遍歷問題,將對聚合的遍歷封裝到一個類中進行,這樣就避免了暴露這個聚合對象的內部表示的可能。
Iterator模式中定義的對外接口可以視客戶成員的便捷定義,但是基本的接口在圖中的Iterator中已經給出了(參考STL的Iterator就知道了)。
討論
Iterator模式的應用很常見,我們在開發中就經常會用到STL中預定義好的Iterator來對STL類進行遍歷(Vector、Set等)。
Interpreter模式提供了這樣的一個實現語法解釋器的框架,筆者曾經也正在構建一個編譯系統Visual CMCS,現在已經發布了Visual CMCS1.0 (Beta),請大家訪問Visual CMCS網站獲取詳細信息。
Interpreter模式中,提供了TerminalExpression和NonterminalExpression兩種表達式的解釋方式,Context類用於爲解釋過程提供一些附加的信息(例如全局的信息)。
Interpreter模式則提供了一種很好的組織和設計這種解析器的架構。
Interpreter模式中使用類來表示文法規則,因此可以很容易實現文法的擴展。另外對於終結符我們可以使用Flyweight模式來實現終結符的共享。