標籤:
[本文首發於cnblogs byeyear Email:[email protected] 轉載請註明]
本文是關於建立型模式的雜談,全文的組織圖比較鬆散,但基本上還是依著原文的編寫順序來談。
A. 約定
使用”產品“來指代中間產品(如Room、Door、Wall等);
使用”成品“來指代最終的產品(如Maze)。
B. 建立型模式分類
建立型模式可分為兩類:類建立型模式和對象建立型模式。
B.1 類建立型模式使用繼承改變被執行個體化的類,例如Factory Method,使用繼承改變產品特性;
B.2 對象建立型模式將執行個體化委託給另一個對象,例如Abstract Factory,將執行個體化委託給具體的工廠類。
C. MapSite類是產品的公用抽象基類:Enter是這些組件都具有的公用操作。
從直觀上(現實角度)來看,Room、Door、Wall這些產品似乎難以有一個公用的基類。可以這樣考慮:首先,Room、Door、Wall這三種產品作為Maze的構成要素,應該是平行的;其次,考慮這三種產品有什麼樣的公用屬性,並將其提取出來作為基類。
D. Room一方面繼承於MapSite,另一方面又包含四面MapSite指標:同時運用了繼承和組合。
E. Maze類是Maze的表示,MazeGame類負責Maze的建立——將Maze的表示和建立分離開來。這有利於將來單獨改變Maze的表示或Maze的建立。比如,Maze可以用鏈表表示,也可以用數組表示,改變其表示方式不會影響到MazeGame類。
F. 原文中所說的”對布局進行寫入程式碼“有兩個方面的意思:一是對迷宮結構的寫入程式碼,二是對迷宮構件的寫入程式碼。前者使得增刪房間等操作變得困難,後者使得難以採用不同特色的構件如BomedRoom。Builder模式是解決前者的,其他模式都用於解決後者。
G. AbstractFactory
G.1 Client僅適用Abstract Factory提供的介面建立對象,而不使用特定的具體類的介面;實際的對象建立由(繼承於Abstract Factory的)Concrete Factory完成。由於各Concrete Factory具有(繼承於Abstract Factory的)完全相同的Product生產介面,所以各個Concrete Factory的產品線必須完全一致。這樣,如果Abstract Factory需要增加新的產品介面,所有Concrete Factory都必須修改。Abstract Factory模式使得各Product之間強相關。假如你有兩個Factory甲和乙,生產三種Product A、B、C,Client要麼使用甲A甲B甲C,要麼使用乙A乙B乙C,而不能同時使用甲A和乙B。這既是優勢也是缺點:這種特性有利於產品的一致性,非常適合於一個系列的產品對象必須一起工作的情況;但對於不同Factory的產品對象要能夠互換的情況顯然不合適。
G.2 典型的Abstract Factory的例子是支援多種視覺風格的使用者介面工具包:介面上的各widget風格必須統一,因此不可能在同一介面上使用多個Widget Factory(不同Concrete Factory的產品不可互換);不同風格的介面總是有相同種類的Widget(不同的Concreate Factory具有相同的產品線)。
G.3 在Abstract Factory中定義產品介面並在Concrete Factory中實現的做法實際上就是Factory Method:將產品的建立延遲到具體的Concrete Factory中。
G.4 原始代碼與採用了Abstract Factory模式的代碼對比:
原始版本:
Maze* MazeGame::CreateMaze()
{
Maze *aMaze = new Maze();
Room *r1 = new Room(1);
}
修改版本:
Maze* MazeGame::CreateMaze(Factory& factory)
{
Maze *aMaze = factory.MakeMaze();
Room *r1 = factory.MakeRoom(1);
}
和原始版本相比,修改版本不直接生產Product,而是在代碼某處new一個Factory出來,由這個Factory生產。
H. Abstract Factory和Factory Method的對比
H.1 Abstract Factory:一般來說,Abstract和Concrete Factory都在“架構”中實現。Client直接使用架構設計者提供的Concrete Factory來建立Product,一般不會去建立新的Factory(Client僅使用Product,而不生產Product)。這是因為Abstract Factory的每次改動都會涉及Concrete Factory,Abstract類和Concrete類是“聯動”的,它們不適合分開在不同的設計者層次中實現。
H.2 Factory Method:架構設計者提供Interface,由Client實現之(Client需要自己生產Product)。
H.3 Abstract Factory中,產品系列總是整體替換;
H.4 Factory Method更適合於對已有產品特性進行修正、更改、擴充。
H.5 Abstract Factory中產品和工廠強關聯。
H.6 Factory Method中產品和Creator關聯不強。比如基類的Creator使用產品X、Y、Z,繼承類可以使用產品X+、Y+、Z,而X+、Y+分別繼承於X、Y。
H.7 Factory Method用於生產過程中部分產品或產品部分特性的修改,或生產過程中某個步驟的具體實現的修改(但步驟或流程本身不變)。
I. Builder Mode
I.1 Builder模式主要講的是如何build,和其他三種模式的關注點不一樣。
I.2 將對象的構建和表示分離,使得同樣的構建過程可以建立不同的表示。
I.3 以RTF轉換器為例子,對同一篇文檔,構建過程是一樣的(ParseRTF),但使用不同的Converter可以得到不同的表示:相同的構建過程,不同的構建結果。
I.4 所有的Builder必須具有相同的抽象借口,這樣不同的Builder之間才能互相替換。
I.5 Director負責下達指令,Builder負責執行指令;一個是領導,一個是員工;Director有圖紙,Builder負責造。
I.6 架構提供各種Builder,Client建立Director對象,並用所需的builder配置它。
I.7 Builder Mode隱藏了產品內部結構。
J. Abstract Factory和Builder模式的聯合應用
Director是設計院,Builder是施工隊,Factory是材料商。Director的七姑的八姨的小舅子是材料商的女兒的同學的媽媽的孩子:
// Director告訴Builder必須使用指定factory提供的材料
void StandardBuilder::BuildRoom(Factory& factory, int n)
{
Room *room = factory.CreateRoom();
......
}
K. Builder模式和Abstract Factory模式
K.1 Abstract Factory著重產品系列整體替換;
K.2 Builder模式著重構建過程。
L. Factory Method模式
L.1 以App/Doc架構為例子, App在使用者從菜單中選擇New時建立Doc。
App知道Doc何時建立,但不知道哪一種Doc子類被建立。
-> 應用情境:知道某個子類何時建立,但暫時還沒有該子類的實現。
App/Doc架構不知道具體子類,因此Doc子類對象的建立只能在App子類而不是App父類裡做,因此App父類需要一個virtual的CreateDocument函數並由App子物件在其中new一個Doc子物件(App子類是知道Doc子類的)。
父類(架構)負責過程(CreateDoc,docs.Add,doc->Open),這個過程對不同的App/Doc是一致的;子類負責過程中需要用到子類的那些步驟的具體實現。
跳出App/Doc架構來看,Factory Method的基類定好產品工序,子類根據需要修改/實現某一道或某幾道工序。改變產品類就必須建立新的子類。
L.2 迷宮遊戲原始版本:
Maze* MazeGame::CreateMaze()
{
Maze *aMaze = new Maze();
Room *r1 = new Room(1);
}
L.3 修改版本:
Maze* MazeGame::CreateMaze()
{
Maze *aMaze = MakeMaze(); // Virtual function
Room *r1 = MakeRoom(1); // Virtual function
}
M. 類平行層次
一個類將它的一些職責委託給一個獨立的類。被委託的職責僅在較少的時候需要,因此不需要被儲存在委託方。
N. ProtoType模式
----------------------------------- 分割線 ----------------------------------
原文的下面這句話似乎翻譯有問題:
……為定義選擇板中的那些工具,還提供一個抽象類別Tool。該架構還為一些建立繪圖物件執行個體並將它們加入到文檔中的工具預定義了一個GraphicTool子類……
這句話的意思應該是這樣:
……為定義選擇板中的那些工具,還提供一個抽象類別Tool。例如,該架構為建立繪圖物件並將它們加入到文檔的工具預定義了一個GraphicTool子類。類似的還有旋轉繪圖物件的RotateTool子類。
----------------------------------- 分割線 ----------------------------------
N.1 GraphicTool用來對Graphic進行操縱,這樣初想起來Manipulate類似乎可以作為Graphic的一個成員。然而這種想法並不好;理由:
1) Manipulate類不是Graphic類自身所具有的某種“屬性”或“方法”,而是外部強加上去的;相比較而言,Draw操作就是Graphic類自身應該具有的。
2) 不同Graphic的同種Manipulate是類似的:比如GraphicTool建立並繪製一個新的Graphic;RotateTool旋轉一個Graphic。
3) 施加於Graphic的Manipulate是不可預測的。現在有了GraphicTool和RotateTool,將來可能會有ScaleTool和FlipTool。
以上,一句話概括,解耦了Graphic類和Tool類。
N.2 Graphic需要動態建立,適合於ProtoType。
N.3 為什麼需要clone而不是new
因為我們無法預Crowdsourced Security Testing道需要建立的物件類型,因此無法new(對於Smalltalk這不是問題;因此這種模式在Smalltalk中用處不大)。用音樂編輯器做例子,使用者想要什麼樣的Graphic,你就要clone什麼樣的Graphic。架構設計者不可能知道client會鼓搗出什麼樣的Graphic,於是要求client必須為他們的Graphic提供clone操作供GraphicTool類使用。
O. 幾種建立型模式對比
O.1 Abstract Factory
A廠家生產A-Wall,A-Room,A-Door
B廠家生產B-Wall,B-Room,B-Door
C廠家生產C-Wall,C-Room,C-Door
各家產品能夠各自配套;建造Maze時可以全用A家的三款產品,也可以全用B家的或全用C家的,但三家產品不能混用。架構設計者已幫你做好三個廠家和他們的所有產品,Client直接用就是了。
不同廠家的同種材料之間是並列關係。
O.2 Factory Method
已經有了基本的磚頭Wall、木質Room和拼木的Door,通過繼承使用刷了漆的Wall、裝修過的Room以及實木的Door。Wall、Room和Door這三款產品可以僅替換其中一個。架構設計者只提供基本的Wall、Room和Door,更精美的Wall、Room和Door由Client通過繼承實現。架構設計者還提供了Maze的構建過程。Client一般不修改構建過程,只通過繼承Maze並在Derived Maze中使用自己的材料來替換標準材料。
同種材料之間存在繼承關係;不存在直接的並列關係。
O.3 PROTOTYPE
有的Client想要A-Wall,B-Door,C-Room;
有的Client想要A-Wall,D-Door,E-Room……
如果是Factory Method,每種組合都需要一個MazeGame子類;使用ProtoType,只要用不同的物件建構MazeFactory。
[本文首發於cnblogs byeyear Email:[email protected] 轉載請註明]
建立型模式雜談