Fork me on GitHub

小菜学习设计模式(四)—原型(Prototype)模式

前言

设计模式目录:

本篇目录:

  其实说到原型模式,大家可能会想到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     }
View Code

  测试代码:

 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.HeartTypebaby2.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.HeartTypebaby2.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.HeartTypebaby2.HeartType的引用地址是不一样的,和上面重新赋值对象的效果是一样的,但是我们调用的时候没有做额外的操作,就可以实现此效果,但是序列化和反序列化是比较耗时的,这点也需要注意下。

  示例代码下载:Prototype.rar

后记

  关于创建型模式上面几篇说的差不多,还有个针对工厂方法模式出现问题的解决方案,下面就是结构型模式了,还在学习中,未完待续。。。

 

posted @ 2014-02-12 09:51 田园里的蟋蟀 阅读(...) 评论(...) 编辑 收藏