设计模式(5) 原型模式

  • 原型模式
  • 原型模式的适用场景
  • 浅拷贝
  • 深拷贝
  • 用Initialize方法修改初始化状态

原型模式与之前学习的各种工厂方法、单例模式、建造者模式最大、最直观的区别在于,它是从一个既有的对象“克隆”出新的对象,而不是从无到有创建一个全新的对象。与对文件的拷贝类似,原型模式是基于现有的对象拷贝新的对象。

原型模式

GOF对原型模式的描述为:
Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype..
— Design Patterns : Elements of Reusable Object-Oriented Software

原型模式的构造对象的的过程是,选择一个现成对象(原型对象),通过调用它的“克隆”方法来获得一个和它一样的对象。
其UML类图为:
原型模式 UML类图

原型模式的适用场景

原型模式适用与如下场景:

  • Factory、Builder、Singleton返回的都是“初始状态”的对象,但有的时候需要的对象反而是处于某种状态的对象;
  • 如果一个对象的初始化需要很多其他对象的数据准备或其他资源的繁琐计算,则可以使用原型模式直接克隆;
  • 当需要一个对象的大量公共信息,少量字段进行个性化设置的时候,也可以使用原型模式拷贝出现有对象的副本进行加工处理。

浅拷贝

拷贝有浅拷贝与深拷贝之分,浅拷贝会复制值类型的字段,但对于引用类型则只复制引用,相当于原型与副本仍然共用同一个引用类型,所以浅拷贝是不彻底的。
而深拷贝则与之相反,深拷贝会开辟一块另外的内存区域,把原型包括值类型和引用类型都逐位复制过去。

在C#中其实有内置的原型模式支持,object类型自带的MemberwiseClone方法实现的是浅拷贝,还有ICloneable接口,在实现这个接口时可以自行决定拷贝的深度。

基于浅拷贝实现的原型模式:

public interface IProtoType
{
    IProtoType Clone();
    string Name { get; set; }
}
public class ConcretePrototype : IProtoType
{
    public string Name { get; set; }

    public IProtoType Clone()
    {
        return (IProtoType)this.MemberwiseClone();
    }
}

深拷贝

深拷贝是把引用目标地址的内容逐个bit地复制一份,看起来简单,但实现起来并不容易,因为成员可能是引用类型,而且可能存在引用类型的嵌套,最正规的方法是通过反射不断深入嵌套结构的内部,类似对树的遍历,碰到引用类型,就重新new一个。还有一种比较取巧的方法是利用二进制、Json、XML等序列化、反序列化来实现。但序列化方式的性能很差,如果拷贝的次数较多,这个劣势会更加明显。
二进制序列化方式相比其他两种更加灵活,可以通过NonSerializedAttribute设置序列化时忽略的属性。
通过二进制序列化来拷贝:

[Serializable]
public class DeepClone : IProtoType
{
    [NonSerialized]
    public List<string> Users = new List<string>();
    public string Name { get; set; }

    public IProtoType Clone()
    {
        string graph = SerializeHelper.BinarySerialize(this);
        return SerializeHelper.BinaryDeSerialize<IProtoType>(graph);
    }
}

//序列化工具类
public class SerializeHelper
{
    public static string BinarySerialize(object graph)
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            IFormatter formatter = new BinaryFormatter();
            formatter.Serialize(memoryStream, graph);
            Byte[] arrGraph = memoryStream.ToArray();
            return Convert.ToBase64String(arrGraph);
        }
    }


    public static T BinaryDeSerialize<T>(string graph)
    {
        Byte[] arrGraph = Convert.FromBase64String(graph);
        using (MemoryStream memoryStream = new MemoryStream(arrGraph))
        {
            IFormatter formatter = new BinaryFormatter();
            return (T)formatter.Deserialize(memoryStream);
        }
    }
}

用Initialize方法修改初始化状态

有些时候,客户程序需要的不仅仅是千篇一律的副本,还要求副本的某些属性具有不同的状态,之前原型模式的适用场景也提到过当需要一个对象的大量公共信息,少量字段进行个性化设置的时候,面对这种需求,如果想要增加几个Clone方法的重载,就会破坏设计模式封装变化的初衷,如果后续还有其他的初始化需求,增加更多的重载方法是不现实的。
《设计模式:可复用面向对象软件的基础》对此有一个建议,就是增加一个名为Initialize()的操作,把不稳定性转嫁到这个方法上。在C#还可以借助params类型特性,支持可变参数。
调用端在Clone操作之后,再调用Initialize方法,并把指定的初始化参数传入以设定对象的内部状态。

参考书籍:
王翔著 《设计模式——基于C#的工程化实现及扩展》

posted @ 2020-07-09 22:11  zhixin9001  阅读(243)  评论(0编辑  收藏  举报