什麼是原型模式?
在GOF的《設計模式:可複用面向對象軟體的基礎》中是這樣說的:用原型執行個體指定建立對象的種類,并且通過拷貝這些原型建立新的對象。這這個定義中,最重要的一個詞是“拷貝”,也就是口頭上的複制,而這個拷貝,也就是原型模式的精髓所在。
舉一個最簡單的例子來說明原型模式:記得上國小的時候,老師把需要做的課外習題寫到黑闆上,而下面的我們都要把這些題抄寫到自己的本子上,回家做好,第二天交上來,也就是每道題,全班50個人,每個人都要抄寫一遍。按照現在的時間理論來說,就是浪費了50個人的時間。但是,那個時候條件限制,老師也是不得已而為之。現在好了,老師做一份電子版的習題,列印一份,然後拿着這份列印的原版,就可以複制出50份。
結合原型模式的概念進行分析,老師列印出來的那一份,就是“原型”,而複制出來的那50份,就是使用的“拷貝”。而原型模式就是這麼簡單的一個道理,通過現有的東西,再複制出一個來。
為什麼要使用原型模式?
原型模式和建造者模式、工廠方法模式一樣,都屬于建立型模式的一種。簡單的來說,我們使用原型模式,就是為了建立對象。但是,在以下場景下,使用原型模式是最好的選擇:
- 當我們的對象類型不是開始就能确定的,而這個類型是在運作期确定的話,那麼我們通過這個類型的對象克隆出一個新的對象比較容易一些;
- 有的時候,我們需要一個對象在某個狀态下的副本,此時,我們使用原型模式是最好的選擇;例如:一個對象,經過一段處理之後,其内部的狀态發生了變化;這個時候,我們需要一個這個狀态的副本,如果直接new一個新的對象的話,但是它的狀态是不對的,此時,可以使用原型模式,将原來的對象拷貝一個出來,這個對象就和之前的對象是完全一緻的了;
- 當我們處理一些比較簡單的對象時,并且對象之間的差別很小,可能就幾個屬性不同而已,那麼就可以使用原型模式來完成,省去了建立對象時的麻煩了;
- 有的時候,建立對象時,構造函數的參數很多,而自己又不完全的知道每個參數的意義,就可以使用原型模式來建立一個新的對象,不必去理會建立的過程,讓建立過程見鬼去吧。
是以,在上述的的情況下,在設計的時候,适當的考慮一下原型模式,減少對應的工作量,減少程式的複雜度,提高效率。
用UML類圖表示原型模式

由于克隆需要一個原型,而上面的類圖中Prototype就這個原型,Prototype定義了克隆自身的Clone接口,由派生類進行實作,而實作原型模式的重點就在于這個Clone接口的實作。ConcretePrototype1類和ConcretePrototype2類繼承自Prototype類,并實作Clone接口,實作克隆自身的操作;同時,在ConcretePrototype1類和ConcretePrototype2類中需要重寫預設的複制構造函數,供Clone函數調用,Clone就是通過在内部調用重寫的複制構造函數實作的。在後續的編碼過程中,如果某個類需要實作Clone功能,就隻需要繼承Prototype類,然後重寫自己的預設複制構造函數就好了。好比在C#中就提供了ICloneable接口,當某個類需要實作原型模式時,隻需要實作這個接口的道理是一樣的。
代碼實作
1 #include <iostream>
2 using namespace std;
3
4 //接口
5 class Prototype
6 {
7 public :
8 Prototype(){}
9 virtual ~Prototype(){}
10
11 virtual Prototype * Clone() = 0;
12 };
13
14 //實作
15 class ConcretePrototype : public Prototype
16 {
17 public :
18 ConcretePrototype():m_counter(0){}
19 virtual ~ConcretePrototype(){}
20
21 //拷貝構造函數
22 ConcretePrototype( const ConcretePrototype & rhs)
23 {
24 m_counter = rhs .m_counter;
25 }
26
27 //複制自身
28 virtual ConcretePrototype * Clone()
29 {
30 //調用拷貝構造函數
31 return new ConcretePrototype (*this );
32 }
33
34 private :
35 int m_counter;
36 };
37
38 int main(int argc , char **argv)
39 {
40 //生成對像
41 ConcretePrototype * conProA = new ConcretePrototype ();
42
43 //複制自身
44 ConcretePrototype * conProB = conProA->Clone();
45
46 delete conProA;
47 conProA= NULL ;
48
49 delete conProB;
50 conProB= NULL ;
51
52 return 0;
53 }
上述代碼實作了一個最簡單的原型模式,但是已經将原型模式的基本實作原理展現出來了。而有的時候,當調用Clone獲得了一個複制的對象以後,需要改變對象的狀态,此時就可能需要在ConcretePrototype類中添加一個Initialize操作,專門用于初始化克隆對象。由于在Clone的内部調用的是複制構造函數,而此處又涉及到深複制和淺複制的問題。是以,在實際操作的過程中,這些問題,都需要進行仔細的考慮。
與其它建立型模式的比較
工廠方法模式、抽象工廠模式、建造者模式和原型模式都是建立型模式。工廠方法模式适用于生産較複雜,一個工廠生産單一的一種産品的時候;抽象工廠模式适用于一個工廠生産多個互相依賴的産品;建造者模式着重于複雜對象的一步一步建立,組裝産品的過程,并在建立的過程中,可以控制每一個簡單對象的建立;原型模式則更強調的是從自身複制自己,建立要給和自己一模一樣的對象。
總結
原型模式作為建立型模式中最特殊的一個模式,具體的建立過程,是由對象本身提供,這樣我們在很多的場景下可以很友善的快速的建構新的對象。但是,原型模式的最大缺點是繼承原型的子類都要實作Clone操作,這個是很困難的。例如,當所考慮的類已經存在時就難以新增Clone操作。當内部包括一些不支援拷貝或者有循環引用的對象時,實作克隆可能也會很困難。說以說,每一種設計模式都有它的優點和缺點,在設計的時候,我們需要進行權衡各方面的因素,揚長避短。