一、 原型(Prototype)模式
原型模式的用意是:通過(guò)給出一個(gè)原型對象來(lái)指明所要創(chuàng )建的對象類(lèi)型,然后用復制這個(gè)原型對象的辦法創(chuàng )建出更多的同類(lèi)型對象。
從孫大圣的手段談起
孫悟空在與黃風(fēng)怪的戰斗中,"使一個(gè)身外身的手段:把毫毛揪下一把,用口嚼得粉碎,望上一噴,叫聲‘變‘,變有百十個(gè)行者,都是一樣得打扮,各執一根鐵棒,把那怪圍在空中。"換而言之,孫悟空可以根據自己的形象,復制出很多"身外身"來(lái)。
老孫這種身外身的手段在面向對象設計領(lǐng)域里叫原型(Prototype)模式。
C#對原型模式的支持
在C#里面,我們可以很容易的通過(guò)Clone()方法實(shí)現原型模式。任何類(lèi),只要想支持克隆,必須實(shí)現C#中的ICloneable接口。ICloneable接口中有一Clone方法,可以在類(lèi)中復寫(xiě)實(shí)現自定義的克隆方法??寺〉膶?shí)現方法有兩種:淺拷貝(shallowcopy)與深拷貝(deep copy)。
(以下摘自:《.NET框架程序設計(修訂版)》,李建忠譯)淺拷貝是指當對象的字段值被拷貝時(shí),字段引用的對象不會(huì )被拷貝。例如,如果一個(gè)對象有一個(gè)指向字符串的字段,并且我們對該對象做了一個(gè)淺拷貝,那么兩個(gè)對象將引用同一個(gè)字符串。而深拷貝是對對象實(shí)例中字段引用的對象也進(jìn)行拷貝的一種方式,所以如果一個(gè)對象有一個(gè)指向字符串的字段,并且我們對該對象做了一個(gè)深拷貝的話(huà),我們將創(chuàng )建一個(gè)新的對象和一個(gè)新的字符串--新對象將引用新字符串。需要注意的是執行深拷貝后,原來(lái)的對象和新創(chuàng )建的對象不會(huì )共享任何東西;改變一個(gè)對象對另外一個(gè)對象沒(méi)有任何影響。
二、 Prototype模式的結構:
客戶(hù)(Client)角色:客戶(hù)類(lèi)提出創(chuàng )建對象的請求。
抽象原型(Prototype)角色:這是一個(gè)抽象角色,通常由一個(gè)C#接口或抽象類(lèi)實(shí)現。此角色給出所有的具體原型類(lèi)所需的接口。在C#中,抽象原型角色通常實(shí)現了ICloneable接口。
具體原型(Concrete Prototype)角色:被復制的對象。此角色需要實(shí)現抽象原型角色所要求的接口。
三、 程序舉例:
下面的程序給出了一個(gè)示意性的實(shí)現:
// Prototype pattern -- Structural example
using System;
// "Prototype"
abstract class Prototype
{
// Fields
private string id;
// Constructors
public Prototype( string id )
{
this.id = id;
}
public string Id
{
get{ return id; }
}
// Methods
abstract public Prototype Clone();
}
// "ConcretePrototype1"
class ConcretePrototype1 : Prototype
{
// Constructors
public ConcretePrototype1( string id ) : base ( id ) {}
// Methods
override public Prototype Clone()
{
// Shallow copy
return (Prototype)this.MemberwiseClone();
}
}
// "ConcretePrototype2"
class ConcretePrototype2 : Prototype
{
// Constructors
public ConcretePrototype2( string id ) : base ( id ) {}
// Methods
override public Prototype Clone()
{
// Shallow copy
return (Prototype)this.MemberwiseClone();
}
}
/**////
/// Client test
/// class Client
{
public static void Main( string[] args )
{
// Create two instances and clone each
ConcretePrototype1 p1 = new ConcretePrototype1( "I" );
ConcretePrototype1 c1 = (ConcretePrototype1)p1.Clone();
Console.WriteLine( "Cloned: {0}", c1.Id );
ConcretePrototype2 p2 = new ConcretePrototype2( "II" );
ConcretePrototype2 c2 = (ConcretePrototype2)p2.Clone();
Console.WriteLine( "Cloned: {0}", c2.Id );
}
}
這個(gè)例子實(shí)現了一個(gè)淺拷貝。其中MemberwiseClone()方法是Object類(lèi)的一個(gè)受保護方法,實(shí)現了對象的淺拷貝。如果希望實(shí)現一個(gè)深拷貝,應該實(shí)現ICloneable接口,并自己編寫(xiě)ICloneable的Clone接口方法。
四、 帶Prototype Manager的原型模式
原型模式的第二種形式是帶原型管理器的原型模式,其UML圖如下:
客戶(hù)(Client)角色:客戶(hù)端類(lèi)向原型管理器提出創(chuàng )建對象的請求。
抽象原型(Prototype)角色:這是一個(gè)抽象角色,通常由一個(gè)C#接口或抽象類(lèi)實(shí)現。此角色給出所有的具體原型類(lèi)所需的接口。在C#中,抽象原型角色通常實(shí)現了ICloneable接口。
具體原型(Concrete Prototype)角色:被復制的對象。此角色需要實(shí)現抽象的原型角色所要求的接口。
原型管理器(Prototype Manager)角色:創(chuàng )建具體原型類(lèi)的對象,并記錄每一個(gè)被創(chuàng )建的對象。
下面這個(gè)例子演示了在原型管理器中存儲用戶(hù)預先定義的顏色原型,客戶(hù)通過(guò)原型管理器克隆顏色對象。
// Prototype pattern -- Real World example
using System;
using System.Collections;
// "Prototype"
abstract class ColorPrototype
{
// Methods
public abstract ColorPrototype Clone();
}
// "ConcretePrototype"
class Color : ColorPrototype
{
// Fields
private int red, green, blue;
// Constructors
public Color( int red, int green, int blue)
{
this.red = red;
this.green = green;
this.blue = blue;
}
// Methods
public override ColorPrototype Clone()
{
// Creates a ‘shallow copy‘
return (ColorPrototype) this.MemberwiseClone();
}
public void Display()
{
Console.WriteLine( "RGB values are: {0},{1},{2}",
red, green, blue );
}
}
// Prototype manager
class ColorManager
{
// Fields
Hashtable colors = new Hashtable();
// Indexers
public ColorPrototype this[ string name ]
{
get{ return (ColorPrototype)colors[ name ]; }
set{ colors.Add( name, value ); }
}
}
/**////
/// PrototypeApp test
/// class PrototypeApp
{
public static void Main( string[] args )
{
ColorManager colormanager = new ColorManager();
// Initialize with standard colors
colormanager[ "red" ] = new Color( 255, 0, 0 );
colormanager[ "green" ] = new Color( 0, 255, 0 );
colormanager[ "blue" ] = new Color( 0, 0, 255 );
// User adds personalized colors
colormanager[ "angry" ] = new Color( 255, 54, 0 );
colormanager[ "peace" ] = new Color( 128, 211, 128 );
colormanager[ "flame" ] = new Color( 211, 34, 20 );
// User uses selected colors
string colorName = "red";
Color c1 = (Color)colormanager[ colorName ].Clone();
c1.Display();
colorName = "peace";
Color c2 = (Color)colormanager[ colorName ].Clone();
c2.Display();
colorName = "flame";
Color c3 = (Color)colormanager[ colorName ].Clone();
c3.Display();
}
}
五、 淺拷貝與深拷貝
下面給出淺拷貝與深拷貝的兩個(gè)例子,例子使用了ICloneable接口。C#中的數組是引用型的變量,我們通過(guò)數組來(lái)進(jìn)行演示:
淺拷貝:
using System;
class ShallowCopy : ICloneable
{
public int[] v = {1,2,3};
public Object Clone()
{
return this.MemberwiseClone();
}
public void Display()
{
foreach(int i in v)
Console.Write( i + ", ");
Console.WriteLine();
}
}
class Client
{
public static void Main()
{
ShallowCopy sc1 = new ShallowCopy();
ShallowCopy sc2 = (ShallowCopy)sc1.Clone();
sc1.v[0] = 9;
sc1.Display();
sc2.Display();
}
}
ShallowCopy對象實(shí)現了一個(gè)淺拷貝,因此當對sc1進(jìn)行克隆時(shí),其字段v并沒(méi)有克隆,這導致sc1與sc2的字段v都指向了同一個(gè)v,因此,當修改了sc1的v[0]后,sc2的v[0]也發(fā)生了變化。
深拷貝:
using System;
class DeepCopy : ICloneable
{
public int[] v = {1,2,3};
// 默認構造函數
public DeepCopy()
{
}
// 供Clone方法調用的私有構造函數
private DeepCopy(int[] v)
{
this.v = (int[])v.Clone();
}
public Object Clone()
{
// 構造一個(gè)新的DeepCopy對象,構造參數為
// 原有對象中使用的 v
return new DeepCopy(this.v);
}
public void Display()
{
foreach(int i in v)
Console.Write( i + ", ");
Console.WriteLine();
}
}
class Client
{
public static void Main()
{
DeepCopy dc1 = new DeepCopy();
DeepCopy dc2 = (DeepCopy)dc1.Clone();
dc1.v[0] = 9;
dc1.Display();
dc2.Display();
}
}
這次在克隆的時(shí)候,不但克隆對象本身,連里面的數組字段一并克隆。因此,最終打印出來(lái)的dc1與dc2不同。
六、 Prototype模式的優(yōu)點(diǎn)與缺點(diǎn)
Prototype模式的優(yōu)點(diǎn)包括
1、Prototype模式允許動(dòng)態(tài)增加或減少產(chǎn)品類(lèi)。由于創(chuàng )建產(chǎn)品類(lèi)實(shí)例的方法是產(chǎn)批類(lèi)內部具有的,因此增加新產(chǎn)品對整個(gè)結構沒(méi)有影響。
2、Prototype模式提供了簡(jiǎn)化的創(chuàng )建結構。工廠(chǎng)方法模式常常需要有一個(gè)與產(chǎn)品類(lèi)等級結構相同的等級結構,而Prototype模式就不需要這樣。
3、Portotype模式具有給一個(gè)應用軟件動(dòng)態(tài)加載新功能的能力。由于Prototype的獨立性較高,可以很容易動(dòng)態(tài)加載新功能而不影響老系統。
4、產(chǎn)品類(lèi)不需要非得有任何事先確定的等級結構,因為Prototype模式適用于任何的等級結構。
Prototype模式的缺點(diǎn):
Prototype模式的最主要缺點(diǎn)就是每一個(gè)類(lèi)必須配備一個(gè)克隆方法。而且這個(gè)克隆方法需要對類(lèi)的功能進(jìn)行通盤(pán)考慮,這對全新的類(lèi)來(lái)說(shuō)不是很難,但對已有的類(lèi)進(jìn)行改造時(shí),不一定是件容易的事。