原型模式与以前学习的各类工厂方法、单例模式、建造者模式最大、最直观的区别在于,它是从一个既有的对象“克隆”出新的对象,而不是从无到有建立一个全新的对象。与对文件的拷贝相似,原型模式是基于现有的对象拷贝新的对象。设计模式
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类图为:
性能
原型模式适用与以下场景:学习
拷贝有浅拷贝与深拷贝之分,浅拷贝会复制值类型的字段,但对于引用类型则只复制引用,至关于原型与副本仍然共用同一个引用类型,因此浅拷贝是不完全的。
而深拷贝则与之相反,深拷贝会开辟一块另外的内存区域,把原型包括值类型和引用类型都逐位复制过去。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); } } }
有些时候,客户程序须要的不单单是千篇一概的副本,还要求副本的某些属性具备不一样的状态,以前原型模式的适用场景也提到过当须要一个对象的大量公共信息,少许字段进行个性化设置的时候,面对这种需求,若是想要增长几个Clone方法的重载,就会破坏设计模式封装变化的初衷,若是后续还有其余的初始化需求,增长更多的重载方法是不现实的。
《设计模式:可复用面向对象软件的基础》对此有一个建议,就是增长一个名为Initialize()的操做,把不稳定性转嫁到这个方法上。在C#还能够借助params类型特性,支持可变参数。
调用端在Clone操做以后,再调用Initialize方法,并把指定的初始化参数传入以设定对象的内部状态。3d
参考书籍:
王翔著 《设计模式——基于C#的工程化实现及扩展》code