from:http://www.cnblogs.com/xishuai/p/3527083.html
本篇目录:
其实说到原型模式,大家可能会想到Clone,其实也不尽然,在我们的日常生活中,原型(Prototype)模式也可以经常看到:
你觉得某位明星的发型很好看,你也想做个和他一样的发型,于是你拿个照片去理发店说:我要做个和这个一模一样的发型。
其实那个明星的发型某种意义上说就是原型,因为已经有发型参考了,所以发型师会很快的做出来。就像我们在编程时,当创建一些对象(特别是大对象)非常耗时时,或者创建过程非常复杂时,原型模式就会很有用。
概述
我们看下GoF对原型(Prototype)模式下的定义:
使用原型模型实例指定将要创建的对象类型,通过复制这个实例创建新的对象。
从这个定义中,我们可以分解下,其实说的意思就是:指定类型,复制对象。就像上面的例子,造型就是一个指定的类型,然后去复制此类型的对象。原型模型的简单静态类图:

Client使用Prototype的Clone()方法得到这个对象的副本,我们先看下引用类型的存储:引用类型的变量在栈中分配一个内存空间,这个内存空间包含的是对另一个内存位置的引用,这个位置是托管堆中的一个地址,即存放此变量实际值的地方。.NET自动维护一个堆指针,它包含堆中下一个可用内存空间的地址。就是说引用类型在栈中存储一个托管堆的地址,托管堆存储这个变量的实际值。上面所说的“复制对象”在原型(Prototype)模式中分为两种,一种是只复制栈中的托管堆地址,就是说副本对象和原对象指向的托管堆的地址是一样的,这种复制称为浅拷贝(Shallow Copy);另一种是栈中存放的地址和托管堆中实际值都复制,就是说生成一个和原对象一样的全新对象,这种复制称为深拷贝(Deep Copy)。
我们用一张示意图可以简单概述下:

其实关于浅拷贝和深拷贝在我们现实生活中也有相应类似的例子,比如双胞胎,长的一样就像一个模子刻出来的一样,而且各自有各自的身体互不影响,在某种意义上说可以称为深拷贝;在双胞胎中也有一些特殊的,比如连体双胞胎,有的两个身体公用一个心脏什么的,怎么说呢,在某种意义上这种你也可以称为浅拷贝。这个例子只是形象说明下原型模式中复制对象的含义,可能有些不恰当的地方。
实现
其实在.net中关于浅拷贝提供了一个方法MemberwiseClone();
MemberwiseClone()方法返回的是Object类型,注意方法的修饰符是protected,就是说,如果想让外部对象使用它,必须在子类重写该方法,设定其访问范围是public。还有就是实现对象的复制必须实现ICloneable接口,并实现其Clone()方法。就上面说的双胞胎的例子,我们做一个简单的示例:
1 /// <summary>
2 /// 心脏类
3 /// </summary>
4 public class Heart
5 {
6 private int _size;
7 private int _volume;
8 /// <summary>
9 /// 大小
10 /// </summary>
11 public int Size
12 {
13 get { return _size; }
14 set { _size = value; }
15 }
16 /// <summary>
17 /// 体积
18 /// </summary>
19 public int Volume
20 {
21 get { return _volume; }
22 set { _volume = value; }
23 }
24 }
25
26 /// <summary>
27 /// baby类
28 /// </summary>
29 public class Baby : ICloneable
30 {
31 private string _name;
32 private string _description;
33 private Heart _hearttype;
34 /// <summary>
35 /// 名称
36 /// </summary>
37 public string Name
38 {
39 get { return _name; }
40 set { _name = value; }
41 }
42 /// <summary>
43 /// 描述
44 /// </summary>
45 public string Description
46 {
47 get { return _description; }
48 set { _description = value; }
49 }
50 /// <summary>
51 /// 心脏特征
52 /// </summary>
53 public Heart HeartType
54 {
55 get { return _hearttype; }
56 set { _hearttype = value; }
57 }
58
59 #region ICloneable 成员
60 public object Clone()
61 {
62 return this.MemberwiseClone();
63 }
64 #endregion
65 }
测试代码:
1 static void Main(string[] args)
2 {
3 Baby baby1 = new Baby();
4 baby1.Name = "I'm baby1";
5 baby1.Description = "I'm baby";
6 baby1.HeartType = new Heart() { Size = 111, Volume = 222 };
7 Baby baby2 = (Baby)baby1.Clone();
8 baby2.Name = "I'm baby2";
9
10 Console.WriteLine("baby1 info:");
11 Console.WriteLine(baby1.Name);
12 Console.WriteLine(baby1.Description);
13 Console.WriteLine("baby2 info:");
14 Console.WriteLine(baby2.Name);
15 Console.WriteLine(baby2.Description);
16 Console.WriteLine("The heart of the different:");
17 Console.WriteLine(baby1.HeartType == baby2.HeartType);
18 }
运行结果:
我们可以看到baby1.HeartType和baby2.HeartType的引用地址是一样的,这种就是浅拷贝(Shallow Copy),就说明baby1和baby2公用一个心脏,是连体双胞胎。
深拷贝(Deep Copy)
其实上面的代码稍微修改下就是深拷贝,如下:
1 static void Main(string[] args)
2 {
3 Baby baby1 = new Baby();
4 baby1.Name = "I'm baby1";
5 baby1.Description = "I'm baby";
6 baby1.HeartType = new Heart() { Size = 111, Volume = 222 };
7 Baby baby2 = (Baby)baby1.Clone();
8 baby2.HeartType = new Heart() { Size = 111, Volume = 222 };//重新创建对象
9 baby2.Name = "I'm baby2";
10
11 Console.WriteLine("baby1 info:");
12 Console.WriteLine(baby1.Name);
13 Console.WriteLine(baby1.Description);
14 Console.WriteLine("baby2 info:");
15 Console.WriteLine(baby2.Name);
16 Console.WriteLine(baby2.Description);
17 Console.WriteLine("The heart of the different:");
18 Console.WriteLine(baby1.HeartType == baby2.HeartType);
19 }
上面给baby2重新创建一个和baby1一样的心脏,而不是公用一个,运行结果:
可以看到baby1.HeartType和baby2.HeartType的引用地址是不一样的,虽然是一样的心脏,但是是两个独立相同的心脏,其实上面的方法并不算是深拷贝,只是实现了深拷贝的效果,因为并不是在拷贝中完成的。这种气势有个不好的地方,当一个对象中有很多对象组合的时候,而且这个对象内部很复杂,我们不可能复制完之后,每个对象的去重新赋值,这样实现深拷贝就没有什么意义。当然还有一种实现深拷贝的方式就是序列化,必须在类的前面加上[Serializable]表示,指示这个类是可以序列化的,我们把Clone()的方法修改下:
1 public object Clone()
2 {
3 object result = null;
4 MemoryStream stream = new MemoryStream();
5 BinaryFormatter formatter = new BinaryFormatter();
6 formatter.Serialize(stream, this);
7 stream.Close();
8 byte[] streamByte = stream.ToArray();
9 MemoryStream stream2 = new MemoryStream(streamByte);
10 result = formatter.Deserialize(stream2);
11 stream2.Close();
12 return result;
13 }
现在Clone()方法做的工作就是序列化和反序列化,我们使用浅拷贝的测试代码,运行结果为:
baby1.HeartType和baby2.HeartType的引用地址是不一样的,和上面重新赋值对象的效果是一样的,但是我们调用的时候没有做额外的操作,就可以实现此效果,但是序列化和反序列化是比较耗时的,这点也需要注意下。
示例代码下载:Prototype.rar
后记
关于创建型模式上面几篇说的差不多,还有个针对工厂方法模式出现问题的解决方案,下面就是结构型模式了,还在学习中,未完待续。。。
