意圖(Intent)#
使用一個原型實例來指定要建立的物件種類,透過複製(clone)此原型來建立新物件。
動機(Motivation)#
假設要為樂譜編輯器建構工具列,框架提供了抽象的 Graphic 類別(代表音符、休止符等圖形元件)與 Tool 類別(代表工具列上的工具),以及一個 GraphicTool 子類別用來建立圖形物件並加入文件。
問題是:GraphicTool 屬於框架,而音符等類別是應用程式特有的。若為每種音符都建立 GraphicTool 的子類別,會產生大量只差在實體化類別的子類別。
解法是讓 GraphicTool 持有一個 prototype——某個 Graphic 子類別的實例。當需要建立新物件時,GraphicTool 複製(clone)這個原型。只要所有 Graphic 子類別都支援 Clone 操作,GraphicTool 就能複製任何 Graphic。
更進一步,全音符和二分音符不一定需要各自的類別——它們可以是同一個 MusicalNote 類別以不同的 bitmap 和 duration 初始化的實例。這大幅減少了系統中的類別數量。
Prototype 模式的核心概念:用物件的複製取代類別的實體化,從而避免建立平行的類別階層,並讓系統在執行時期就能擴展產品種類。
適用場景(Applicability)#
當系統需要獨立於產品的建立方式,且符合下列任一條件:
- 要實體化的類別在執行時期才指定(如動態載入)
- 想避免建立與產品階層平行的工廠階層
- 類別的實例只有少數幾種狀態組合,安裝對應數量的原型並 clone 比每次手動設定更方便
結構(Structure)#
Client 持有 Prototype 的參照。當需要新物件時,Client 要求 Prototype clone 自己。ConcretePrototype 實作 Clone 操作,回傳自身的副本。
classDiagram
class Prototype {
<<interface>>
+Clone()
}
class ConcretePrototype1 {
+Clone()
}
class ConcretePrototype2 {
+Clone()
}
class Client {
+Operation()
}
Prototype <|.. ConcretePrototype1
Prototype <|.. ConcretePrototype2
Client --> Prototype參與者(Participants)#
| 參與者 | 範例 | 職責 |
|---|---|---|
| Prototype | Graphic | 宣告 clone 自身的介面 |
| ConcretePrototype | Staff、WholeNote、HalfNote | 實作 clone 自身的操作 |
| Client | GraphicTool | 透過要求 prototype clone 自身來建立新物件 |
協作方式(Collaborations)#
- Client 要求 prototype clone 自己,取得新物件
優缺點(Consequences)#
優點:
- 執行時期新增與移除產品 —— 只需向 client 註冊新的原型實例即可引入新產品,比其他 creational pattern 更具彈性
- 透過改變值來定義新物件 —— 在高度動態的系統中,透過物件組合與屬性值設定(而非定義新類別)來定義新行為。clone 原型近似於實體化類別
- 透過改變結構來定義新物件 —— 支援複合物件(如電路由子電路組成)的原型化,只要 composite 物件的 Clone 實作 deep copy
- 減少子類別 —— 不需要平行的 Creator 階層,clone 原型取代 factory method 建立物件
- 動態載入類別 —— 在 C++ 等靜態語言中,可透過原型管理器讓應用程式使用動態載入的類別
缺點:
- 每個子類別都必須實作 Clone —— 當既有類別要加入 Clone 支援可能很困難,尤其當物件內部包含不支援複製的物件或有循環參照時
Clone 的實作需要仔細處理淺複製 vs. 深複製的問題。對於包含複雜內部結構的原型,通常需要 deep copy 才能確保 clone 與原型相互獨立。
實作要點(Implementation)#
- Prototype Manager(原型管理器) —— 當原型數量不固定時,使用一個關聯式儲存(registry)來管理原型。Client 以 key 查詢並 clone 原型,不需要自行管理原型物件。Client 可在執行時期註冊、移除、瀏覽原型
- 實作 Clone 操作
- 大多數語言提供基本的複製支援(Smalltalk 的
copy、C++ 的 copy constructor),但需注意 shallow copy vs. deep copy - 簡單結構用 shallow copy 通常足夠
- 複雜結構需要 deep copy 以確保 clone 與原型獨立
- 可利用 Save/Load 機制實作預設的 Clone——將物件存入記憶體緩衝區再載回
- 大多數語言提供基本的複製支援(Smalltalk 的
- 初始化 clone —— 部分 client 需要在 clone 後設定物件的內部狀態。由於 Clone 的參數數量因類別而異,無法在 Clone 操作中傳入。可透過既有的 setter 方法,或引入專用的
Initialize操作來接受初始化參數
若系統中原型種類會動態變化,務必實作 Prototype Manager。它就像一個型錄(catalog),讓 client 以名稱查詢原型,無需在程式碼中寫死所有原型類別。
已知應用(Known Uses)#
- Sketchpad(Ivan Sutherland)—— 可能是 Prototype 模式最早的應用
- ThingLab —— 使用者可組合物件並將其升級為原型,放入可重用物件庫
- Etgdb(ET++ 的除錯器前端)—— 從全域表中以名稱查詢 DebuggerAdaptor 原型並 clone
- Mode Composer —— 將各種互動技術物件存為原型,支援無限的互動技術擴展
- Unidraw 繪圖框架
相關模式(Related Patterns)#
- Prototype 與 Abstract Factory 在某些方面是競爭關係,但也可搭配使用——Abstract Factory 可儲存一組原型來 clone 並回傳產品
- 大量使用 Composite 與 Decorator 的設計通常也能從 Prototype 獲益