定义:指定创建对象的种类,并且通过拷贝这些原型对象创建新的对象。
这里提到的原型对象就是被克隆的对象。
当创建一个类的实例的过程很昂贵或很复杂,并且需要多次创建这样的类实例时,如果我们用 new 操作去创建这样的类实例,会增加创建类对象的复杂度和消耗更多的内存空间,因为内存中分配了多个这样的实例对象。而使用原型模式就可以在不需要关心任何创建细节,又大大提高性能的前提下完成对象的创建。
使用场景
(1) 当一个系统应该独立于它的产品创建、构成和表示时
(2) 为了避免创建一个与产品类层次平行的工厂类层次时
(3) 当要实例化的类是在运行时指定时
(4) 当一个类的实现只能有几个不同状态组合中的一种时,建立相应数目的原型并克隆原型可能比每次用合适的状态手工实例化原型类更方便一些。
原型模式类图如下
Prototype 抽象原型角色
通常由一个接口或者抽象类实现,给出所有具体原型类所需要的接口。在C# 中,抽象原型角色通常实现了 ICloneable 接口。
ConcretePrototype 具体原型角色
被拷贝的对象,需要实现抽象原型提供的接口
Client 客户角色
使用原型对象的客户程序
上边提到的拷贝、克隆 分为浅拷贝与深拷贝
浅拷贝:
它创建一个新对象,这个对象有着原始对象(被拷贝对象)属性值的一份精确拷贝。
如果属性是基本类型时,浅拷贝会复制该属性的值,赋值给新对象。
如果属性是引用类型时,浅拷贝复制的是引用数据类型的地址值,这种情况下,当拷贝出来的某一个类修改了引用数据类型的值,所有拷贝出来的以及原始对象都会发生改变。
深拷贝:
它创建一个新对象,这个对象有着原始对象(被拷贝对象)属性值的一份精确拷贝。
如果属性是基本类型时,深拷贝会复制该属性的值,赋值给新对象。
如果属性是引用类型时,深拷贝会给引用类型数据的变量申请新的存储空间,并复制引用数据类型成员变量的对象,这样拷贝出来的所有对象单独改变数据类型的值,不会影响其他对象的值。
实例如下
以一个汽车的类,汽车有名字、生产日期、价格 等基本数据
内饰信息,内饰是一个引用类型的数据,有内饰类
代码实现如下
Prototype 抽象原型角色
public interface IClone
{
// 浅拷贝
object ShallowClone();
// 深拷贝
object DeepClone();
// 深拷贝2
object DeepClone2();
}
ConcretePrototype 具体原型角色
[Serializable]
public class Car : IClone
{
// 车品牌名
public string name;
// 生产日期
public string date;
// 价格
public float price;
// 内饰
public AutomotiveTrim automotiveTrim;
// 浅拷贝
public object ShallowClone()
{
// 浅拷贝
return MemberwiseClone();
}
// 深拷贝
public object DeepClone()
{
// 通过序列化和反序列化进行深拷贝
// 通过序列化和反序列化需要在类上加标签 [Serializable]
BinaryFormatter bf = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
bf.Serialize(ms, this); // 复制到流中
ms.Position = 0;
return (bf.Deserialize(ms));
}
// 深拷贝2
public object DeepClone2()
{
// 通过对每个对象成员进行复制进行深拷贝
Car car = new Car();
// 创建新的引用类型对象
car.automotiveTrim = new AutomotiveTrim();
car.automotiveTrim.seat = this.automotiveTrim.seat;
car.automotiveTrim.centerControl = this.automotiveTrim.centerControl;
// 值类型直接赋值
car.name = this.name;
car.date = this.date;
car.price = this.price;
return car;
}
public void Print()
{
string msg = string.Format("{0}_{1}_{2}_{3}_{4} n", name, date, price, automotiveTrim.seat, automotiveTrim.centerControl);
Console.WriteLine(msg);
}
}
Client 客户角色
public class Client
{
public Client()
{
Car car = new Car();
car.name = "宾利飞驰";
car.date = "2021-9-10";
car.price = 290;
car.automotiveTrim = new AutomotiveTrim();
car.automotiveTrim.seat = "带记忆自动调节、加热、通风、按摩座椅";
car.automotiveTrim.centerControl = "触控式液晶屏、语音控制";
// 浅拷贝
Car car1 = car.ShallowClone() as Car;
// 第一种深拷贝方式
Car car2 = car.DeepClone() as Car;
// 第二种深拷贝方式
Car car3 = car.DeepClone2() as Car;
// 测试结果
car.Print();
car1.Print();
car2.Print();
car3.Print();
// 测试修改第一种深拷贝方式
car2.price = 270;
car2.automotiveTrim.seat = "座椅修改了";
Console.WriteLine("n 测试修改第一种深拷贝方式");
car.Print();
car1.Print();
car2.Print();
car3.Print();
// 测试修改第二种深拷贝方式
car3.date = "2021-11-12";
car3.price = 300;
car3.automotiveTrim.centerControl = "中控修改了";
Console.WriteLine("n 测试修改第二种深拷贝方式");
car.Print();
car1.Print();
car2.Print();
car3.Print();
// 测试修改浅拷贝对象
car1.price = 285;
car1.date = "2021-6-30";
car1.automotiveTrim.seat = "座椅修改";
car1.automotiveTrim.centerControl = "中控修改";
Console.WriteLine("n 测试修改浅拷贝对象");
car.Print();
car1.Print();
car2.Print();
car3.Print();
}
}
测试结果如下
优点:
(1) 向客户隐藏了创建新实例的复杂性
(2) 允许动态添加或减少产品类
(3) 简化了实例的创建结构,工厂方法需要一个与产品类等级结构相同的等级结构,而原型模式不需要
(4) 产品类不需要事先确定产品的等级结构,因为原型模式适用于任何等级结构
缺点:
(1) 每个类必须配置一个克隆方法
(2) 配置克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。



