设计模式学习之Prototype模式
原型模式的UML如下:
其中IPrototype定义了符合原型模式要求的类型特征,即至少有一个Clone()方法,客户端只依赖该接口。下面直接上例子吧,例子很简单,根据上面的类图敲出的代码:
C#
2 {
3 string Name { get; set; }
4 //PeopleEntity PeoPle { get; }
5 IPrototype Clone();
6 }
7 //public class PeopleEntity { }
2 {
3 public string Name { get; set; }
4 //public PeopleEntity PeoPle { get { return this.people; } }
5 //private PeopleEntity people = new PeopleEntity();
6
7 public IPrototype Clone()
8 {
9 return (IPrototype)this.MemberwiseClone();
10 }
11 }
UnitTest
2 public void PrototypeTest()
3 {
4 IPrototype sample = new ConcretePrototype();
5 var clone = sample.Clone();
6 Assert.IsNull(sample.Name);
7 Assert.IsNull(clone.Name);
8
9 sample.Name = "A";
10 clone = sample.Clone();
11 Assert.AreEqual<string>("A", clone.Name);
12 Assert.IsInstanceOfType(clone, typeof(ConcretePrototype));
13 clone.Name = "B";
14 Assert.IsTrue(sample.Name != clone.Name);
15 //Assert.AreEqual<int>(sample.PeoPle.GetHashCode(), clone.PeoPle.GetHashCode());
16 }
上面的例子可以得到以下的结论:
2.副本和原型之间相互独立,改变其中一个不会影响另一个
但是这里用到的复制方法是MemberwiseClone(),一般翻译为浅复制、映像复制,它只会把栈中的数据逐位复制到一块新的空间。简单的说只会复制值类型,对于引用类型的只会复制引用而不复制对象,导致副本和原型的引用成员指向同一个对象(string是特殊的引用类型,只读,在这里表现为值类型)。把上面代码中注释去掉,即可验证这一点,会发现副本和原型引用成员PeoPle的HashCode是一致的,即同一个对象。
为了解决这个问题就要引入深复制(Deep Copy),它会将栈和堆中的数据全部都复制到另一块新开辟的内存中,这样副本和原型就完全独立了。浅复制最简单直接调用MemberwiseClone()方法就可以了,那深复制如何实现呢?首先想到的是手工逐层完成,遇到引用类型就要在新对象中重新new()一个独立实例,这太麻烦了,也很容易出错;另一个办法就是序列化:先将原型中的数据全部都序列化到string中,再反序列化,这样就得到了一个与原型完全一样,但却是在另一片内存中新对象即副本。这种解决办法很实用,只要为目标类型标记“Serializable” 或“NonSerialized”的标签(Attribute)即可,对于复杂的情况自己也可以定制ISerializable的序列化过程。下面上例子:
Prototype Class
2 public class UserInfo
3 {
4 public string name;
5 public readonly List<string> Education = new List<string>();
6
7 public UserInfo GetShallowCopy()
8 {
9 return this.MemberwiseClone() as UserInfo;
10 }
11 public UserInfo GetDeepCopy()
12 {
13 return SerializationHelper.DeserializeStringToObject<UserInfo>(SerializationHelper.SerializeObjectToString(this));
14 }
15 }
UnitTest
2 public void ShallCopyTest()
3 {
4 var user1 = new UserInfo();
5 user1.name = "Hans";
6 user1.Education.Add("A");
7 var user2 = user1.GetShallowCopy();
8
9 Assert.AreEqual<string>(user1.name, user2.name);
10 Assert.AreEqual<string>(user1.Education[0], user2.Education[0]);
11
12 user2.Education[0] = "B";
13 Assert.AreNotEqual<string>("A", user1.Education[0]);
14 }
15
16 [TestMethod]
17 public void DeepCopyTest()
18 {
19 var user1 = new UserInfo();
20 user1.name = "Hans";
21 user1.Education.Add("A");
22 var user2 = user1.GetDeepCopy();
23
24 Assert.AreEqual<string>(user1.name, user2.name);
25 Assert.AreEqual<string>(user1.Education[0], user2.Education[0]);
26
27 user2.Education[0] = "B";
28 Assert.AreEqual<string>("A", user1.Education[0]);
29 }
对于原型类中不想或不需要被复制的成员可以为其标上“NonSerialized”的标签即可实现简单的定制复制过程。在注重性能的场合,应该尽量控制复制的范围,仅需要把最小集做进去,而不是图省事把实例整体复制,即只在需要复制的成员上标记“Serializable”。
如果遇到更精细的复制需求呢,比如说上面的Education成员我们只需要前3条记录(如高中以后),这时就需要自己去实现ISerializable接口,定制符合需求的序列化过程,实现个性化复制。下面就直接上代码吧:
2 public class CustomizedUserInfo : ISerializable
3 {
4 public string Name { get; set; }
5 public int Age { get; set; }
6 public string[] Education { get; set; }
7
8 public CustomizedUserInfo() { }
9
10 // Deserialization process
11 public CustomizedUserInfo(SerializationInfo info, StreamingContext context)
12 {
13 Name = info.GetString("Name");
14 Education = info.GetValue("Education", typeof(string[])) as string[];
15 }
16 // Customized serialization content process
17 // Implement interface method.Will be automatically invoked when Serialization
18 public void GetObjectData(SerializationInfo info, StreamingContext context)
19 {
20 info.AddValue("Name", this.Name);
21 // Only serialize the first 3 items according to the demand
22 info.AddValue("Education", this.Education.Take(3).ToArray());
23 }
24 public CustomizedUserInfo GetCustomizedDeepCopy()
25 {
26 return SerializationHelper.DeserializeStringToObject<CustomizedUserInfo>(SerializationHelper.SerializeObjectToString(this));
27 }
28 }
Unit Test
2 public void CustomizedCopyTest()
3 {
4 var user = new CustomizedUserInfo()
5 {
6 Name = "Hans",
7 Age = 20,
8 Education = new string[] { "A", "B", "C", "D", "E" }
9 };
10 var clone = user.GetCustomizedDeepCopy();
11
12 // Only serialize the first 3 items
13 Assert.AreEqual<int>(3, clone.Education.Length);
14 CollectionAssert.AreEqual(new string[] { "A", "B", "C" }, clone.Education);
15 // Age is not serialized
16 Assert.AreNotEqual<int>(user.Age, clone.Age);
17 // Name serialized
18 Assert.AreEqual<string>(user.Name, user.Name);
19 }
附上SerializationHelper工具类:
2 {
3 public static string SerializeObjectToString(object graph)
4 {
5 using (MemoryStream memoryStream = new MemoryStream())
6 {
7 IRemotingFormatter formatter = new BinaryFormatter();
8 formatter.Serialize(memoryStream, graph);
9 Byte[] arrGraph = memoryStream.ToArray();
10 return Convert.ToBase64String(arrGraph);
11 }
12 }
13
14 public static T DeserializeStringToObject<T>(string serializedGraph)
15 {
16 Byte[] arrGraph = Convert.FromBase64String(serializedGraph);
17 using (MemoryStream memoryStream = new MemoryStream(arrGraph))
18 {
19 IRemotingFormatter formatter = new BinaryFormatter();
20 return (T)formatter.Deserialize(memoryStream);
21 }
22 }
23 }
下面做个小结吧,原型模式是创建型模式中比较强调产品个体特征的模式,通过自我复制的过程实现新的实例创建,不同于工厂类。项目中,使用原型模式的关键不在于定于一个IPrototype接口,而是如何又好又快的按照需求完成复制过程。
出处:http://www.cnblogs.com/Hans2Rose/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。