• 设计模式学习之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接口,而是如何又好又快的按照需求完成复制过程。

  • 相关阅读:
    弄明白python reduce 函数
    Linux 下载百度网盘大文件
    java 从网上下载文件的几种方式
    Windows下Python2与Python3两个版本共存的方法详解
    python 学习笔记
    Glide实现查看图片和保存图片到手机
    Android Animation 知识点速记备忘思维导图
    You must not call setTag() on a view Glide is targeting when use Glide
    前端数据流哲学
    精读《Optional chaining》
  • 原文地址:https://www.cnblogs.com/Hans2Rose/p/PrototypePattern.html
Copyright © 2020-2023  润新知