Creational Patterns簡介
創造(Creational)設計模式抽象了實例化的過程。它使得系統可以獨立於對象的創建、組成和表示。一個類的Creational模式利用繼承機制來改變它實例化的類。
當一個系統發展到更依賴於對象組合而不是類的繼承時,Creational模式變得尤其重要。在這種情況下,不能將一些程序功能寫死,而是定義一個小一點的基本功能集合,然後由其去構成其它複雜的功能。因此,創建特定功能的對象不僅僅是簡單地實例化一個類。
在這裏需要強調兩點:第一,Creational模式都封裝了該系統所需的具體類的內容;第二,這種模式隱藏了這些具體類如何創建和如何被放在一起的。在什麼被創建、由誰創建、如何創建和何時創建,這幾個方面Creational模式給予你了很大的靈活性。這種模式可以讓你的系統配置結構和功能上廣泛變化的“產品(product)”對象。這種配置可以是靜態的(在編譯時指定),或者是動態的(運行時)。
一些Creational模式是競爭關係。例如,有些案例中,Prototype和Abstract Factory模式都可以發揮很好的作用。有些時候,它們又相互補充:Builder模式可以使用一種其它的模式來構建組件。Prototype在實現的時候可以用Singleton。
因爲這些Creational模式是緊密關聯的,我們將要學習5種這種模式,來對比它們之間異同。我們將會使用一個實際案例–建立一個迷宮遊戲–來展現具體的實現。這個迷宮遊戲的具體設計在講述不同的模式時可能會有些變化。有時,這個遊戲就是尋找一條出去的路,所以玩家將會智能看到迷宮的局部。有時,迷宮又會有各種難題和危險,迷宮將會展現出已經探索過的區域。
我們將會忽略迷宮的很多細節,也不必關係是單個玩家或者多個玩家。我們只關心迷宮如何構建的。我們將迷宮第一成一個房間的集合。一個房間會知道它的鄰居,它的鄰居可能是其它房間、一堵牆或者一個通向其它房間的門。
Room、Door和Wall類定義得了迷宮(maze)的主要組件。我們在這裏只定義了主要部分,我們忽略了玩家,操作和移動等其它和構建迷宮無關的功能。
下圖展示了這些類之間的關係:
每個房間有四個邊。我們使用了C++中的枚舉方向Direction來指定房間的東西南北邊:
enum Direction{North,Sourth,East,West};
在Smalltalk中的實現使用了對應的符號來表示方向。
類MapSit
是迷宮所有組件的抽象類。爲了簡化例子,MapSit
定義了一個操作Enter
,意思是說明你如何進入的。如果你進入一個房間,那麼你的位置就會發生改變。
Enter
提供爲其它遊戲操作提供了一個簡單的基礎。
Room
是MapSite
的具體的一個類,定義了迷宮中組件的重要關係。它擁有指向其它MapSite
對象的引用,同時存儲了房間號。這個號碼在迷宮中唯一標識這個房間。
下面的類標識牆或者門:
我們不僅需要表示迷宮的一部分,我們還需要表示迷宮整體。Maze
可以使用RoomNo
和房間號找到指定的房間。
RoomNo
可以使用線性搜索來查詢,或者hash表,或者一個簡單的數組。我們在這裏先忽略這些具體細節,我們只關係如何指定maze對象的組件。
我們定義了另一個類MazeGame
,用來創建迷宮。一個直接的方法是通過一些列操作在迷宮中來添加組件,同時將它們連接起來。例如,下面的成員函數將會創建兩個房間和一個它們之間的門:
這個函數很複雜,因爲它只是建立了兩個房間,顯然還有其它更簡單的方法。例如,Room
的constructor可以提前初始化牆的邊,但這只是將代碼轉移到其它地方去了。這個函數最致命的問題不是它的代碼行數,而是它的靈活性,它寫死了迷宮的佈局。修改佈局需要重新寫這個函數,或者重寫(overriding)這個函數–也就是重新實現–或者修改一部分–這樣做會易出錯的,同時不利於重用。
Creational模式告訴我們如何將這設計的更加靈活,不僅僅於更少的代碼。特別的是,這樣做可以使得改變迷宮組件的類很容易。
假設在一個新的魔法迷宮遊戲中,你想重用現有的迷宮佈局代碼。這個魔法迷宮使用了新的組件,例如DoorNeedingSpell
,一個可以利用咒語來鎖上和打開的門;還有EnchantedRoom
,一個可能有神奇物品的房間,像魔法鑰匙和咒語。如何簡單的改變CreateMaze
,讓它可以創建這個新的迷宮呢?
如果
Createmaze
調用虛函數而不是具體的room、wall和door的構建函數,你使用MazeGame
的子類和重新定義的虛函數來改變實例化的類。這是一個Factory Method模式的例子。如果
Createmaze
被傳入一個對象來創建room、wall和door。你可以通過傳入不同的參數來修改這些組件的類。這是一個Abstract Factory模式的例子。如果
Createmaze
被傳入一個對象,這個對象可以在內部創建一個新的迷宮,同時可以向這個迷宮添加room、door和wall。你可以使用繼承來修改這個迷宮和這個迷宮建立的方式。這是一個Builder模式的例子。如果
Createmaze
以各種原型room、door和wall對象參數化,然後它將這些組件複製添加進這個maze。你可以通過替換這些原型對象來改變maze。這是一個Prototype的例子。
最後剩下的一個Creational模式是Singleton,可以保證每個遊戲只有唯一的迷宮,同時所有遊戲對象可以訪問這個迷宮,而不需要求助於全局變量和函數。Singleton 方便的擴展和替換maze,而不需修改現有的代碼。
(本系列文章是對《Design Patterns: Elements of Reusable Object-Oriented Software》的部分翻譯和個人理解,如有錯誤,歡迎批評指正)