设计模式(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返回的都是“初始状态”的对象,但有的时候须要的对象反而是处于某种状态的对象;
  • 若是一个对象的初始化须要不少其余对象的数据准备或其余资源的繁琐计算,则可使用原型模式直接克隆;
  • 当须要一个对象的大量公共信息,少许字段进行个性化设置的时候,也可使用原型模式拷贝出现有对象的副本进行加工处理。

浅拷贝

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

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

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

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方法,并把指定的初始化参数传入以设定对象的内部状态。3d

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

相关文章
相关标签/搜索