一、基础概念
原型模式的本质是【克隆生成对象】;
原型模式的定义: 用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象 。
原型模式的功能: 1、通过克隆来创建新的对象实例; 2、为克隆出来的新对象实例复制原型实例属性值;
**克隆:**无论是自己实现克隆方法,还是采用C#提供的克隆方法,都存在一个浅度克隆和深度克隆的问题:
1、浅度克隆: 只负责克隆按值传递的数据(比如基本数据类型、String类型);
2、深度克隆 :除了浅度克隆要克隆的值外,还负责克隆引用类型的数据,基本上就是被克隆实例所有的属性数据都会被克隆出来;
|----|------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------|
| 序号 | 原型模式的优点 | 原型模式的缺点 |
| 1 | 对客户端隐藏具体的实现类型 (即:原型模式的客户端只知道原型接口类型,并不知道具体的实现类型, 从而减少了客户端对具体实现类型的依赖) | 每个原型的子类都必须实现克隆操作,尤其在包含引用类型的对象时,克隆方法会比较麻烦,必须要能够递归地让所有相关对象都要正确地实现克隆 |
| 2 | 在运行时动态改变具体的实现类型 (即:原型模式可以在运行期间,由客户来注册符合原型接口的实现类型,也可以动态改变具体的实现类型,看起来接口没有任何变化,但其实运行的已经是另外一个类实例了【因为克隆一个原型就类似于实例化一个类】) | 每个原型的子类都必须实现克隆操作,尤其在包含引用类型的对象时,克隆方法会比较麻烦,必须要能够递归地让所有相关对象都要正确地实现克隆 |
[原型模式的优缺点]
何时选用原型模式?
1、如果一个系统想要独立于它想要使用的对象时【让系统只面向接口编程,在系统需要新对象时可以通过克隆原型获取】;
2、如果需要实例化的类是在运行时动态指定的,可通过克隆原型类得到想要的实例。
二、原型模式示例
**业务需求:**比如我们有一个订单处理功能,需要保存订单业务(在这个业务功能中,每当订单的预订数量超过1000的时候,就需要将订单拆分为两份订单保存;如果拆成了两份订单后,数量还是超过1000,则继续拆分,直到每份订单的数量不超过1000);且这个订单类型会分为两种(一种是个人订单;一种是公司订单),无论何种订单类型都需要按照业务规则处理。
2.1、不使用模式的示例
既然有两种订单类型,且都要实现保存订单相关业务的通用功能,那么我们可以定义一个接口来声明这些功能行为,然后在定义具体的类分别实现即可:
1、定义订单接口
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern
{
/// <summary>
/// 订单接口
/// </summary>
internal interface IOrder
{
//获取订单产品数量
int GetOrderProductNumber();
//设置订单产品数量
void SetOrderProductNumber(int productNumber);
}//Interface_end
}
2、分别定义个人订单与企业订单类来实现接口定义的功能行为
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern
{
/// <summary>
/// 个人订单对象
/// </summary>
internal class PersonalOrder : IOrder
{
//消费者名称
public string? CustomerName;
//产品编号
public string? ProductId;
//产品订单数量
private int productOrderNumber = 0;
public int GetOrderProductNumber()
{
return productOrderNumber;
}
public void SetOrderProductNumber(int productNumber)
{
//做各种逻辑校验内容,此处省略
this.productOrderNumber = productNumber;
}
public override string ToString()
{
string str=$"个人订单的订购人是【{CustomerName},订购的产品是【{ProductId}】,订购数量是【{productOrderNumber}】】";
return str;
}
}//Class_end
}
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern
{
/// <summary>
/// 企业订单对象
/// </summary>
internal class EnterpriseOrder : IOrder
{
//企业名称
public string? EnterpriseName;
//产品编号
public string? ProductId;
//产品的订单数量
private int productOrderNumber=0;
public int GetOrderProductNumber()
{
return productOrderNumber;
}
public void SetOrderProductNumber(int productNumber)
{
productOrderNumber = productNumber;
}
public override string ToString()
{
string str = $"企业订单的订购企业是【{EnterpriseName},订购的产品是【{ProductId}】,订购数量是【{productOrderNumber}】】";
return str;
}
}//Class_end
}
3、现在的中心任务就是要实现《保存订单》的业务方法
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern
{
/// <summary>
/// 处理订单的业务对象
/// </summary>
internal class OrderBussiness
{
//固定的数量
private const int fixedNumber = 1000;
/// <summary>
/// 保存订单
/// </summary>
/// <param name="order">订单</param>
public void SaveOrder(IOrder order)
{
/*根据业务要求,当预订产品的数量大于1000时,就需要把订单拆分为两份*/
//1、判断订单是否大于1000(若大于1000则拆分订单)
while (order.GetOrderProductNumber()>1000)
{
//2.创建一份新订单,这份订单传入的订单除了数量不一样,其他都相同
IOrder newOrder = null;
if (order is PersonalOrder)
{
//创建相应的新订单对象
PersonalOrder newPO = new PersonalOrder();
//将传入订单的数据赋值给新订单对象
PersonalOrder po = (PersonalOrder)order;
newPO.CustomerName = po.CustomerName;
newPO.ProductId = po.ProductId;
newPO.SetOrderProductNumber(fixedNumber);
//将个人订单对象内容赋值给新订单
newOrder = newPO;
}
else if (order is EnterpriseOrder)
{
EnterpriseOrder newEO = new EnterpriseOrder();
EnterpriseOrder eo = (EnterpriseOrder)order;
newEO.EnterpriseName = eo.EnterpriseName;
newEO.ProductId = eo.ProductId;
newEO.SetOrderProductNumber(fixedNumber);
newOrder=newEO;
}
//3、设置拆分后的订单数量
order.SetOrderProductNumber(order.GetOrderProductNumber() - fixedNumber);
//4、处理业务功能
Console.WriteLine($"拆分生成的订单是【{newOrder}】");
}
//订单数量不超过1000的直接执行业务处理
Console.WriteLine($"拆分生成的订单是【{order}】");
}
}//Class_end
}
4、编写客户端测试
cs
using System.Net.Sockets;
namespace PrototypePattern
{
internal class Program
{
static void Main(string[] args)
{
OrderBussinessTest();
Console.ReadLine();
}
/// <summary>
/// 处理订单的业务对象测试
/// </summary>
private static void OrderBussinessTest()
{
Console.WriteLine("---处理订单的业务对象测试---");
/*个人订单*/
Console.WriteLine("\n\n个人订单\n");
//创建订单对象并设置内容(为了演示简单直接new)
PersonalOrder po = new PersonalOrder();
po.CustomerName = "张三";
po.ProductId = $"CK{new Random(Guid.NewGuid().GetHashCode()).Next(100,999)}";
po.SetOrderProductNumber(2966);
//获取订单业务对象(为了演示简单直接new)
OrderBussiness ob=new OrderBussiness();
//保存订单业务
ob.SaveOrder(po);
/*企业订单*/
Console.WriteLine("\n\n企业订单\n");
EnterpriseOrder eo=new EnterpriseOrder();
eo.EnterpriseName = "牛奶咖啡科技有限公司";
eo.ProductId= $"CK{new Random(Guid.NewGuid().GetHashCode()).Next(100, 999)}";
eo.SetOrderProductNumber(3001);
OrderBussiness ob2 = new OrderBussiness();
ob2.SaveOrder(eo);
}
}//Class_end
}
5、运行结果如下:

6、有何问题?
不使用模式的示例是实现了我们需要的保存订单业务功能;但是存在两个问题:
《1》既然我们想要通用的保存订单业务功能,那么实现对象是不应该知道订单的具体对象和具体实现,更不能依赖订单的具体实现;而上面的示例很明显的依赖了具体对象和具体实现;
《2》不使用模式的示例在实现业务功能的时候是很难扩展新的订单类型(即:如果我们现在又增加了几种订单类型,那么还需要在保存订单业务方法里面添新类型的处理,很繁琐,不优雅)。
2.2、使用原型模式的示例
其实上面不使用模式的示例暴露的问题总结起来就是:【我们已经有了具体的实例对象,如何能够在不修改业务方法的情况下快速的使用更多新增的对象】而原型模式刚好就是解决这个问题的。
1、定义订单接口规范产品功能行为
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern.Prototype
{
/// <summary>
/// 订单接口
/// </summary>
internal interface IOrder
{
//获取订单产品数量
int GetOrderProductNumber();
//设置订单产品数量
void SetOrderProductNumber(int productNumber);
//克隆方法
IOrder Clone();
}//Interface_end
}
2、创建个人订单对象与企业订单对象继承接口实现具体功能行为
注意:关于这里的克隆方法不能直接使用【return this】来写,这是因为若这样设置,那么每次克隆客户端获取的都是同一个实例,都指向同一个内存空间,此时只要修改克隆出来的实例对象就会影响到原型对象的实例,这是不可取的;【正确地做法是:直接先new一个自己的对象实例,然后再把自己实例的数据取出来赋值到新对象实例中去】如下所示:
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern.Prototype
{
/// <summary>
/// 个人订单对象
/// </summary>
internal class PersonalOrder : IOrder
{
//订购人员名称
public string? CustomerName;
//产品编号
public string? ProductId;
//订单产品数量
private int productOrderNumber=0;
public IOrder Clone()
{
//创建一个新订单对象,然后把该实例的数据赋值给新对象【浅度克隆】
PersonalOrder po = new PersonalOrder();
po.CustomerName = this.CustomerName;
po.ProductId = this.ProductId;
po.SetOrderProductNumber(this.productOrderNumber);
return po;
}
public int GetOrderProductNumber()
{
return productOrderNumber;
}
public void SetOrderProductNumber(int productNumber)
{
//做各种逻辑校验内容,此处省略
this.productOrderNumber = productNumber;
}
public override string ToString()
{
string str = $"个人订单的订购人是【{CustomerName},订购的产品是【{ProductId}】,订购数量是【{productOrderNumber}】】";
return str;
}
}//Class_end
}
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern.Prototype
{
/// <summary>
/// 企业订单对象
/// </summary>
internal class EnterpriseOrder : IOrder
{
//企业名称
public string? EnterpriseName;
//产品编号
public string? ProductId;
//产品的订单数量
private int productOrderNumber = 0;
public IOrder Clone()
{
//创建一个新订单对象,然后把该实例的数据赋值给新对象【浅度克隆】
EnterpriseOrder eo = new EnterpriseOrder();
eo.EnterpriseName = this.EnterpriseName;
eo.ProductId = this.ProductId;
eo.SetOrderProductNumber(this.productOrderNumber);
return eo;
}
public int GetOrderProductNumber()
{
return productOrderNumber;
}
public void SetOrderProductNumber(int productNumber)
{
//做各种逻辑校验内容,此处省略
this.productOrderNumber = productNumber;
}
public override string ToString()
{
string str = $"企业订单的订购企业是【{EnterpriseName},订购的产品是【{ProductId}】,订购数量是【{productOrderNumber}】】";
return str;
}
}//Class_end
}
3、创建一个类构建通用的保存订单业务方法且不依赖具体的实例对象、方法
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern.Prototype
{
/// <summary>
/// 处理订单的业务对象
/// </summary>
internal class OrderBussiness
{
private const int fixedNumber = 1000;
public void SaveOrder(IOrder order)
{
/*根据业务要求,当预订产品的数量大于1000时,就需要把订单拆分为两份*/
//1、判断订单是否大于1000(若大于1000则拆分订单)
while (order.GetOrderProductNumber()>1000)
{
//2、创建一份新的订单,除了订单的数量不一样,其他内容都一致
IOrder newOrder = order.Clone();
//3、然后进行赋值
newOrder.SetOrderProductNumber(fixedNumber);
//4、创建新订单后原订单需要将使用的数量减去
order.SetOrderProductNumber(order.GetOrderProductNumber()-fixedNumber);
//5、处理业务功能
Console.WriteLine($"拆分生成的订单是【{newOrder}】");
}
//订单数量不超过1000的直接执行业务处理
Console.WriteLine($"拆分生成的订单是【{order}】");
}
}//Class_end
}
4、客户端测试原型模式
cs
using System.Net.Sockets;
namespace PrototypePattern
{
internal class Program
{
static void Main(string[] args)
{
OrderBussinessPrototypeTest();
Console.ReadLine();
}
/// <summary>
/// 处理订单业务原型模式测试
/// </summary>
private static void OrderBussinessPrototypeTest()
{
Console.WriteLine("---处理订单业务原型模式测试---");
/*个人订单*/
Console.WriteLine("\n\n个人订单\n");
//创建订单对象并设置内容(为了演示简单直接new)
Prototype.PersonalOrder po = new Prototype.PersonalOrder();
po.CustomerName = "张三";
po.ProductId = $"CK{new Random(Guid.NewGuid().GetHashCode()).Next(100, 999)}";
po.SetOrderProductNumber(2966);
//获取订单业务对象(为了演示简单直接new)
Prototype.OrderBussiness ob = new Prototype.OrderBussiness();
//保存订单业务
ob.SaveOrder(po);
/*企业订单*/
Console.WriteLine("\n\n企业订单\n");
Prototype.EnterpriseOrder eo = new Prototype.EnterpriseOrder();
eo.EnterpriseName = "牛奶咖啡科技有限公司";
eo.ProductId = $"CK{new Random(Guid.NewGuid().GetHashCode()).Next(100, 999)}";
eo.SetOrderProductNumber(3001);
Prototype.OrderBussiness ob2 = new Prototype.OrderBussiness();
ob2.SaveOrder(eo);
}
}//Class_end
}
5、运行结果

可以看到我们使用原型模式也成功实现了业务功能,并且我们现在扩展新的订单类型后也十分简单,直接用新订单类型实例调用业务方法即可,而不用对业务类方法进行任何修改。
2.3、原型实例与克隆实例
2.3.1、自己手动实现克隆方法
原型实例与克隆出来的实例,本质上是不同的实例(克隆完成后,它们之间是没有关联,互不影响的,也就是说它们所指向不同的内存空间)如下所示:
cs
using System.Net.Sockets;
namespace PrototypePattern
{
internal class Program
{
static void Main(string[] args)
{
TestPrototypeInstaceAndCloneInstace();
Console.ReadLine();
}
/// <summary>
/// 【浅度克隆】原型实例与克隆出来的实例,本质上是不同的实例(克隆完成后,它们之间是没有关联,互不影响的)
/// </summary>
private static void TestPrototypeInstaceAndCloneInstace()
{
//先创建原型实例
Prototype.PersonalOrder order = new Prototype.PersonalOrder();
//设置原型实例的订单数量
order.SetOrderProductNumber(666);
//为了演示简单,就只输出数量
Console.WriteLine($"第一次获取个人订单对象实例的数量【{order.GetOrderProductNumber()}】");
//通过克隆来获取实例
Prototype.PersonalOrder order2 = (Prototype.PersonalOrder)order.Clone();
//修改克隆实例的数量
order2.SetOrderProductNumber(33);
//输出数量
Console.WriteLine($"克隆实例的数量【{order2.GetOrderProductNumber()}】");
//输出原型实例的数量
Console.WriteLine($"克隆实例修改数量后原型实例的数量是【{order.GetOrderProductNumber()}】");
}
}//Class_end
}
运行结果如下:

2.3.2、C#中的克隆方法
在C#语言中已经提供了克隆方法,定义在Object类中;需要克隆功能的类,只需要继承【System.ICloneable】接口即可;如下为演示C#克隆方法示例:
ICloneable.Clone 方法 (System) | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/api/system.icloneable.clone?view=net-7.0Object.MemberwiseClone 方法 (System) | Microsoft Learn
https://learn.microsoft.com/zh-cn/dotnet/api/system.object.memberwiseclone?view=net-9.01、创建接口
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern.CSharpClone
{
/// <summary>
/// 订单接口
/// </summary>
internal interface IOrder2
{
//获取订单产品数量
int GetOrderProductNumber();
//设置订单产品数量
void SetOrderProductNumber(int productNumber);
}//Interface_end
}
2、创建具体的个人订单象继承订单接口与C#克隆接口实现功能
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern.CSharpClone
{
/// <summary>
/// 订单对象【继承C#克隆接口】
/// </summary>
internal class PersonalOrder2 : IOrder2, ICloneable
{
//订购人员名称
public string? CustomerName;
//产品编号
public string? ProductId;
//订单产品数量
private int productOrderNumber = 0;
public object Clone()
{
//直接调用父类的克隆方法【浅度克隆】
object obj = base.MemberwiseClone();
return obj;
}
public int GetOrderProductNumber()
{
return this.productOrderNumber;
}
public void SetOrderProductNumber(int productNumber)
{
//做各种逻辑校验内容,此处省略
this.productOrderNumber = productNumber;
}
public override string ToString()
{
string str = $"个人订单的订购人是【{CustomerName},订购的产品是【{ProductId}】,订购数量是【{productOrderNumber}】】";
return str;
}
}//Class_end
}
3、客户端调用测试
cs
using PrototypePattern.PrototypeeManager;
using System.Net.Sockets;
namespace PrototypePattern
{
internal class Program
{
static void Main(string[] args)
{
TestPrototypeInstaceAndCloneInstace2();
Console.ReadLine();
}
/// <summary>
/// 【浅度克隆】原型实例与克隆出来的实例,本质上是不同的实例(克隆完成后,它们之间是没有关联,互不影响的)
/// </summary>
private static void TestPrototypeInstaceAndCloneInstace2()
{
//先创建原型实例
CSharpClone.PersonalOrder2 order = new CSharpClone.PersonalOrder2();
//设置原型实例的订单数量
order.SetOrderProductNumber(666);
//为了演示简单,就只输出数量
Console.WriteLine($"第一次获取个人订单对象实例的数量【{order.GetOrderProductNumber()}】");
//通过克隆来获取实例
CSharpClone.PersonalOrder2 order2 = (CSharpClone.PersonalOrder2)order.Clone();
//修改克隆实例的数量
order2.SetOrderProductNumber(33);
//输出数量
Console.WriteLine($"克隆实例的数量【{order2.GetOrderProductNumber()}】");
//输出原型实例的数量
Console.WriteLine($"克隆实例修改数量后原型实例的数量是【{order.GetOrderProductNumber()}】");
}
}//Class_end
}
运行结果:

2.4、深度克隆
深度克隆: 除了浅度克隆要克隆的值外,还负责克隆引用类型的数据,基本上就是被克隆实例所有的属性数据都会被克隆出来(如果被克隆的对象里面属性数据是引用类型,也就是属性类型也是对象,则需要一直递归地克隆下去【也就是说,要想深度克隆成功,必须要整个克隆所涉及的对象都要正确实现克隆方法,如果其中的一个没有正确实现克隆,那么就会导致克隆失败】)。
2.4.1、自己实现原型的深度克隆
1、定义产品接口规范产品行为功能
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern.CSharpClone
{
/// <summary>
/// 定义一个克隆产品自身的接口
/// </summary>
internal interface IProductPrototype
{
//克隆产品自身的方法
IProductPrototype CloneProduct();
}//Interface_end
}
2、定义一个产品对象,继承产品接口并实现克隆功能
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern.CSharpClone
{
/// <summary>
/// 产品对象
/// </summary>
internal class Product : IProductPrototype
{
//产品编号
public string? ProductId;
//产品名称
public string? ProductName;
public IProductPrototype CloneProduct()
{
//创建一个新订单,然后把本实例的数据复制过去
Product product = new Product();
product.ProductId = ProductId;
product.ProductName = ProductName;
return product;
}
public override string ToString()
{
string str = $"产品编号【{ProductId}】产品名称【{ProductName}】";
return str;
}
}//Class_end
}
3、订单对象的添加产品对象属性
cs
using PrototypePattern.CSharpClone;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern.Prototype
{
/// <summary>
/// 个人订单对象
/// </summary>
internal class PersonalOrder4 : IOrder
{
//订购人员名称
public string? CustomerName;
//产品编号
public string? ProductId;
//产品对象【新增的产品对象引用类型】
public CSharpClone.Product? Product;
//订单产品数量
private int productOrderNumber=0;
public IOrder Clone()
{
//创建一个新订单对象,然后把该实例的数据赋值给新对象【浅度克隆】
PersonalOrder4 po = new PersonalOrder4();
po.CustomerName = this.CustomerName;
po.ProductId = this.ProductId;
po.SetOrderProductNumber(this.productOrderNumber);
/*自己实现深度克隆也不是很复杂,但是比较麻烦,如果产品类中又有属性是引用类型,
* 在产品类实现克隆方法的时候,则需要调用那个引用类型的克隆方法了。这样一层层的调用下去,
* 如果中途有任何一个对象没有正确实现深度克隆,那将会引起错误
*/
//对于对象类型的数据,深度克隆的时候需要继续调用整个对象的克隆方法【体现深度克隆】
po.Product = (CSharpClone.Product)this.Product.CloneProduct();
return po;
}
public int GetOrderProductNumber()
{
return productOrderNumber;
}
public void SetOrderProductNumber(int productNumber)
{
//做各种逻辑校验内容,此处省略
this.productOrderNumber = productNumber;
}
public override string ToString()
{
string str = $"个人订单的订购人是【{CustomerName},订购的产品是【{ProductId}】,订购数量是【{productOrderNumber}】】,产品对象是【{Product}】】";
return str;
}
}//Class_end
}
4、客户端测试
cs
using System.Net.Sockets;
namespace PrototypePattern
{
internal class Program
{
static void Main(string[] args)
{
OrderBussinessTestDeepClone();
Console.ReadLine();
}
/// <summary>
/// 【深度克隆】处理订单的业务对象测试
/// </summary>
private static void OrderBussinessTestDeepClone()
{
Console.WriteLine("---处理订单的业务对象测试【深度克隆】---");
/*个人订单*/
Console.WriteLine("\n\n个人订单\n");
//创建订单对象并设置内容(为了演示简单直接new)
Prototype.PersonalOrder4 po = new Prototype.PersonalOrder4();
po.CustomerName = "张三";
po.ProductId = $"CK{new Random(Guid.NewGuid().GetHashCode()).Next(100, 999)}";
po.SetOrderProductNumber(2966);
//实例化产品类且指定所有属性的值
CSharpClone.Product product = new CSharpClone.Product();
product.ProductName = "产品1";
product.ProductId = "XCKX006";
//个人订单对象的产品赋值
po.Product = product;
Console.WriteLine($"这是第一次获取的个人订单对象实例【{po}】");
//通过克隆来获取新实例
Prototype.PersonalOrder4 po2 = (Prototype.PersonalOrder4)po.Clone();
//修改克隆实例的值
po2.CustomerName = "李四";
po2.ProductId = $"2CK{new Random(Guid.NewGuid().GetHashCode()).Next(100, 999)}";
po2.SetOrderProductNumber(3666);
po2.Product.ProductName = "产品2";
po2.Product.ProductId = "YYCKYY009";
//输出克隆实例的
Console.WriteLine($"输出克隆出来的个人订单对象实例【{po2}】");
//再次输出原型的实例
Console.WriteLine($"这是第二次获取的个人订单对象实例【{po}】");
}
}//Class_end
}
运行结果如下:

通过自己实现深度克隆可以了解其中原理;其实自己实现深度克隆也不是很复杂,只是比较麻烦。若产品类中又有属性是引用类型,在产品实现克隆方法的时候,则需要调用那个引用类型的克隆方法;需要这样一层层对的调用下去;但中途若有任何一个对象没有正确实现深度克隆,就会引起错误 。
2.4.2、C#中的深度克隆
1、让产品对象继承C#的克隆接口【ICloneable】
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern.CSharpClone
{
/// <summary>
/// 产品对象
/// </summary>
internal class Product2 : ICloneable
{
//产品编号
public string? ProductId;
//产品名称
public string? ProductName;
public object Clone()
{
//直接使用C#的克隆方法,不用自己手动给属性逐一赋值
object obj = base.MemberwiseClone();
return obj;
}
public override string ToString()
{
string str = $"产品编号【{ProductId}】产品名称【{ProductName}】";
return str;
}
}//Class_end
}
2、实现个人订单对象添加产品属性内容
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern.CSharpClone
{
internal class PersonalOrder5 : IOrder2, ICloneable
{
//订购人员名称
public string? CustomerName;
//产品编号
public string? ProductId;
//产品对象【新增的产品对象引用类型】
public Product2? Product2;
//订单产品数量
private int productOrderNumber = 0;
public object Clone()
{
//直接调用C#的克隆方法【浅度克隆】
PersonalOrder5 obj = (PersonalOrder5)base.MemberwiseClone();
//必须手工针对每一个引用类型的属性进行克隆
obj.Product2 = (Product2)this.Product2.Clone();
return obj;
}
public int GetOrderProductNumber()
{
return this.productOrderNumber;
}
public void SetOrderProductNumber(int productNumber)
{
//做各种逻辑校验内容,此处省略
this.productOrderNumber = productNumber;
}
public override string ToString()
{
string str = $"个人订单的订购人是【{CustomerName},订购的产品是【{ProductId}】,订购数量是【{productOrderNumber}】,产品对象是【{Product2}】】";
return str;
}
}//Class_end
}
3、客户端测试
cs
using PrototypePattern.PrototypeeManager;
using System.Net.Sockets;
namespace PrototypePattern
{
internal class Program
{
static void Main(string[] args)
{
OrderBussinessTestDeepClone2();
Console.ReadLine();
}
/// <summary>
/// 【深度克隆】处理订单的业务对象测试
/// </summary>
private static void OrderBussinessTestDeepClone2()
{
Console.WriteLine("---处理订单的业务对象测试【深度克隆】---");
/*个人订单*/
Console.WriteLine("\n\n个人订单\n");
//创建订单对象并设置内容(为了演示简单直接new)
CSharpClone.PersonalOrder5 po = new CSharpClone.PersonalOrder5();
po.CustomerName = "张三";
po.ProductId = $"CK{new Random(Guid.NewGuid().GetHashCode()).Next(100, 999)}";
po.SetOrderProductNumber(2966);
//实例化产品类且指定所有属性的值
CSharpClone.Product2 product2 = new CSharpClone.Product2();
product2.ProductName = "产品1";
product2.ProductId = "XCKX006";
//个人订单对象的产品赋值
po.Product2 = product2;
Console.WriteLine($"这是第一次获取的个人订单对象实例【{po}】");
//通过克隆来获取新实例
CSharpClone.PersonalOrder5 po2 = (CSharpClone.PersonalOrder5)po.Clone();
//修改克隆实例的值
po2.CustomerName = "李四";
po2.ProductId = $"2CK{new Random(Guid.NewGuid().GetHashCode()).Next(100, 999)}";
po2.SetOrderProductNumber(3666);
po2.Product2.ProductName = "产品2";
po2.Product2.ProductId = "YYCKYY009";
//输出克隆实例的
Console.WriteLine($"输出克隆出来的个人订单对象实例【{po2}】");
//再次输出原型的实例
Console.WriteLine($"这是第二次获取的个人订单对象实例【{po}】");
}
}//Class_end
}
4、运行结果如下:

2.5、原型管理器
如果一个系统中的原型数目不固定(如:原型可以被动态的创建和销毁)那么久需要再系统中维护一个当前可用的原型注册表(也称为原型管理器);有了原型管理器后,除了向原型管理器里面添加原型对象的时候是通过new来创建对象的,其余时候都是通过原型管理器来请求原型实例,然后通过克隆方法来获取新对象实例,就可以动态的管理原型了。
1、定义原型接口规范行为功能
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern.PrototypeeManager
{
/// <summary>
/// 原型管理器接口
/// </summary>
internal interface IPrototypeManager
{
IPrototypeManager Clone();
string GetName();
void SetName(string name);
}//Interface_end
}
2、定义类对象原型继承接口实现具体功能
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern.PrototypeeManager
{
internal class ConcreatePrototype1 : IPrototypeManager
{
private string name;
public IPrototypeManager Clone()
{
ConcreatePrototype1 cp=new ConcreatePrototype1();
cp.SetName(name);
return cp;
}
public string GetName()
{
return name;
}
public void SetName(string name)
{
this.name = name;
}
public override string ToString()
{
string str = $"这是具体的原型一,名称是【{name}】";
return str;
}
}//Class_end
}
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern.PrototypeeManager
{
internal class ConcreatePrototype2 : IPrototypeManager
{
private string name;
public IPrototypeManager Clone()
{
ConcreatePrototype2 cp=new ConcreatePrototype2();
cp.SetName(name);
return cp;
}
public string GetName()
{
return name;
}
public void SetName(string name)
{
this.name = name;
}
public override string ToString()
{
string str = $"这是具体的原型二,名称是【{name}】";
return str;
}
}//Class_end
}
3、实现原型管理器
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace PrototypePattern.PrototypeeManager
{
internal class PrototypeManager
{
//定义一个字典来记录原型编号与原型实例的对应关系
private static Dictionary<string,IPrototypeManager> dicPrototype=new Dictionary<string,IPrototypeManager>();
//私有化构造方法,避免外部私自创建实例
private PrototypeManager()
{
}
/// <summary>
/// 添加原型
/// </summary>
/// <param name="prototypeId">原型编号</param>
/// <param name="prototype">原型实例</param>
public static void AddPrototype(string prototypeId,IPrototypeManager prototype)
{
if (string.IsNullOrEmpty(prototypeId) || prototype == null)
{
string str = $"原型编号或者原型不能为空,请检查后重试!";
Console.WriteLine(str);
return;
}
if (!dicPrototype.ContainsKey(prototypeId))
{
dicPrototype.Add(prototypeId, prototype);
}
else
{
string str = $"当前已经存在编号为【{prototypeId}】的原型【{prototype}】,不用重复添加!!!";
Console.WriteLine(str);
}
}
/// <summary>
/// 删除原型
/// </summary>
/// <param name="prototypeId">原型编号</param>
public static void DelPrototype(string prototypeId)
{
if (string.IsNullOrEmpty(prototypeId))
{
string str = $"原型编号不能为空,请检查后重试!";
Console.WriteLine(str);
return;
}
dicPrototype.Remove(prototypeId);
}
/// <summary>
/// 获取原型
/// </summary>
/// <param name="prototypeId">原型编号</param>
/// <returns></returns>
public static IPrototypeManager GetPrototype(string prototypeId)
{
IPrototypeManager prototype = null;
if (string.IsNullOrEmpty(prototypeId))
{
string str = $"原型编号不能为空,请检查后重试!";
Console.WriteLine(str);
return prototype;
}
if (dicPrototype.ContainsKey(prototypeId))
{
prototype = dicPrototype[prototypeId];
return prototype;
}
else
{
Console.WriteLine($"你希望获取的原型还没注册或已被销毁!!!");
return prototype;
}
}
/// <summary>
/// 修改原型
/// </summary>
/// <param name="prototypeId">原型编号</param>
/// <param name="prototype">原型实例</param>
public static void ModifyPrototype(string prototypeId, IPrototypeManager prototype)
{
if (string.IsNullOrEmpty(prototypeId) || prototype == null)
{
string str = $"原型编号或者原型不能为空,请检查后重试!";
Console.WriteLine(str);
return;
}
if (dicPrototype.ContainsKey(prototypeId))
{
dicPrototype[prototypeId] = prototype; ;
}
else
{
string str = $"当前不存在编号为【{prototypeId}】的原型,无法修改!!!";
Console.WriteLine(str);
}
}
}//Class_end
}
4、客户端使用原型管理器
cs
using PrototypePattern.PrototypeeManager;
using System.Net.Sockets;
namespace PrototypePattern
{
internal class Program
{
static void Main(string[] args)
{
PrototypeManagerTest();
Console.ReadLine();
}
/// <summary>
/// 原型管理器测试
/// </summary>
private static void PrototypeManagerTest()
{
Console.WriteLine("---原型管理器测试---");
//初始化原型管理器
string prototypeId = "原型一";
IPrototypeManager pm = new ConcreatePrototype1();
PrototypeManager.AddPrototype(prototypeId,pm);
//1、获取原型来创建对象
IPrototypeManager pm1 = PrototypeManager.GetPrototype(prototypeId).Clone();
pm1.SetName("张三");
Console.WriteLine($"第一个实例是【{pm1}】");
//2、有人动态的切换
string prototypeId2 = "原型二";
IPrototypeManager pm2 = new ConcreatePrototype2();
PrototypeManager.AddPrototype(prototypeId2,pm2);
//3、重新获取原型创建对象
IPrototypeManager pm3 = PrototypeManager.GetPrototype(prototypeId2).Clone();
pm3.SetName("李四");
Console.WriteLine($"第二个实例是【{pm3}】");
//4、有人注销了原型
PrototypeManager.DelPrototype(prototypeId);
//5、再次获取原型一来创建对象
IPrototypeManager pm4 = PrototypeManager.GetPrototype(prototypeId).Clone();
pm4.SetName("王五");
Console.WriteLine($"第三个实例是【{pm4}】");
}
}//Class_end
}
5、运行结果:
