设计模式学习之Prototype模式

 

    原型(Prototype)模式:用原型实例指定创建对象的种类,并通过复制这些原型创建新的对象。其实就是从一个对象再创建另外一个可定制的对象,而且可以不需要知道创建的细节。当一个对象生成不是通过New而是通过复制旧对象的时候,可以考虑使用原型模式。

    原型模式的UML如下:

    其中IPrototype定义了符合原型模式要求的类型特征,即至少有一个Clone()方法,客户端只依赖该接口。下面直接上例子吧,例子很简单,根据上面的类图敲出的代码:

    C#

1     public interface IPrototype
2     {
3         string Name { getset; }
4         //PeopleEntity PeoPle { get; }
5         IPrototype Clone();
6     }
7     //public class PeopleEntity { }

 1     public class ConcretePrototype : IPrototype
 2     {
 3         public string Name { getset; }
 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

 1         [TestMethod()]
 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         }

 上面的例子可以得到以下的结论:

    1.克隆出的副本与原型具有一致的类型和状态(值)
    2.副本和原型之间相互独立,改变其中一个不会影响另一个

    但是这里用到的复制方法是MemberwiseClone(),一般翻译为浅复制、映像复制,它只会把栈中的数据逐位复制到一块新的空间。简单的说只会复制值类型,对于引用类型的只会复制引用而不复制对象,导致副本和原型的引用成员指向同一个对象(string是特殊的引用类型,只读,在这里表现为值类型)。把上面代码中注释去掉,即可验证这一点,会发现副本和原型引用成员PeoPle的HashCode是一致的,即同一个对象。

    为了解决这个问题就要引入深复制(Deep Copy),它会将栈和堆中的数据全部都复制到另一块新开辟的内存中,这样副本和原型就完全独立了。浅复制最简单直接调用MemberwiseClone()方法就可以了,那深复制如何实现呢?首先想到的是手工逐层完成,遇到引用类型就要在新对象中重新new()一个独立实例,这太麻烦了,也很容易出错;另一个办法就是序列化:先将原型中的数据全部都序列化到string中,再反序列化,这样就得到了一个与原型完全一样,但却是在另一片内存中新对象即副本。这种解决办法很实用,只要为目标类型标记“Serializable” 或“NonSerialized”的标签(Attribute)即可,对于复杂的情况自己也可以定制ISerializable的序列化过程。下面上例子:

    Prototype Class

 1     [Serializable]
 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

 1         [TestMethod]
 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接口,定制符合需求的序列化过程,实现个性化复制。下面就直接上代码吧:

    CustomizedUserInfo
 1     [Serializable]
 2     public class CustomizedUserInfo : ISerializable
 3     {
 4         public string Name { getset; }
 5         public int Age { getset; }
 6         public string[] Education { getset; }
 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

 1         [TestMethod]
 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工具类:

SerializationHelper
 1     public static class 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接口,而是如何又好又快的按照需求完成复制过程。

 

posted @ 2012-09-07 00:28  Hans Huang  Views(558)  Comments(0Edit  收藏  举报