英文: Prototype Pattern
原型模式说白了就是对已有对象进行的内存级别的克隆,从而产生一个新的对象。 使用原型模式创建对象的意义在于:
当new一个对象需要消耗很多的资源,而且这个对象提供给多个其他的对象、或者有多个线程调用的时候,使用原型模式创建对象可以大大减少资源开销以及提升对象创建速度。
这里的资源可以包含多个方面,包括数据、硬件资源等等。 比如当创建一个大对象的时候,需要访问它的创建权限、需要去数据表中查询相应的数据,需要调用别的模块生成相应的数据等等,这些操作都将影响一个对象的创建速度,这时候考虑原型模式是一个不错的选择。但是使用原型模式之前也需要考虑克隆得到的对象参数值是否是想要的,一般情况下,调用者对于原型模式产生对象都需要对对象值按需进行修改。所以使用原型模式也得考虑原始克隆对象“干净程度”。
public class PrototypePattern{private static PrototypePattern _instance = null;private PrototypePattern() { }static PrototypePattern(){_instance = new PrototypePattern();}/// <summary>/// 直接克隆/// </summary>/// <returns></returns>public static PrototypePattern Clone(){// 克隆return _instance.MemberwiseClone() as PrototypePattern;}public void Show(){Console.WriteLine("Prototype Pattern");}}
以上的代码很决绝,直接构造函数私有化了,对象的创建全部靠调用Clone()方法。其实对于原型模式本身没有强制要求要怎么怎么做,具体的实现参照具体的业务,但是在C#中基本上就是通过MemberwiseClone()方法来进行对象的克隆(先不考虑序列化方式)。该方法定义如下:
// 创建当前 Object 的浅表副本并返回
protected object MemberwiseClone ();
如上,我们知道MemberwiseClone()只是一个浅拷贝,官方描述如下:
https://docs.microsoft.com/zh-cn/dotnet/api/system.object.memberwiseclone?view=net-5.0#System_Object_MemberwiseClone 方法创建一个 MemberwiseClone 浅表副本,方法是创建一个新的对象,然后将当前对象的非静态字段复制到新的对象。 如果字段是值类型,则执行字段的逐位副本。 如果字段是引用类型,则会复制引用,但不会复制引用的对象;因此,原始对象及其复本引用相同的对象。
所以当我们使用MemberwiseClone()进行浅拷贝时,对于对象中的应用类型的成员是不会拷贝的,只是复制了引用指向的地址。验证如下:
public class Prototype
{
public List<string> Codes = new List<string>();
public Prototype Clone()
{
return this.MemberwiseClone() as Prototype;
}
}
上面创建一个类Prototype, 并提供了对象克隆的方法Clone(), 同时这个类还有引用类型的字段。对他调用clone方法,然后修改数据,对比如下:
Prototype prototype1 = new Prototype();
Prototype prototype2 = prototype1.Clone();
prototype1.Codes.Add("01");
prototype1.Codes.Add("02");
prototype2.Codes.Add("03");
prototype2.Codes.Add("04");
Console.WriteLine($"prototype1.Codes--->{string.Join(", ", prototype1.Codes)}");
Console.WriteLine($"prototype2.Codes--->{string.Join(", ", prototype2.Codes)}");
// 运行输出如下:
prototype1.Codes--->01, 02, 03, 04
prototype2.Codes--->01, 02, 03, 04
可以看到我们克隆的对象和第一次new的对于对应的Codes字段其实是一个引用,也就证明了MemberwiseClone其实就是一个浅拷贝。如果要实现深拷贝,官方也给了以下几种方法:
- 调用要复制的对象的类构造函数,以创建具有从第一个对象获取的属性值的第二个对象。 这假设对象的值由其类构造函数完全定义。
- 调用 MemberwiseClone 方法以创建对象的浅表副本,然后将新对象的值与原始对象的值相同,分配给任何属性或其值为引用类型的字段。
- 序列化要深层复制的对象,然后将序列化的数据还原到其他对象变量。
- 使用带有递归的反射来执行深层复制操作。
我们简单使用第二种方法对上面的Clone进行重写如下:
public class Prototype2
{
public List<string> Codes = new List<string>();
public Prototype2 Clone()
{
Prototype2 instance = this.MemberwiseClone() as Prototype2;
if (instance != null)
// 这里重新初始化字段Codes
instance.Codes = new List<string>();
return instance;
}
}
测试以上代码
Prototype2 prototype1 = new Prototype2();
Prototype2 prototype2 = prototype1.Clone();
prototype1.Codes.Add("01");
prototype1.Codes.Add("02");
prototype2.Codes.Add("03");
prototype2.Codes.Add("04");
Console.WriteLine($"prototype1.Codes--->{string.Join(", ", prototype1.Codes)}");
Console.WriteLine($"prototype2.Codes--->{string.Join(", ", prototype2.Codes)}");
// 运行输出如下:
prototype1.Codes--->01, 02
prototype2.Codes--->03, 04
已经达到了深拷贝的目的。
