学习设计模式《八》——原型模式
一、基础概念
原型模式的本质是【克隆生成对象】;
原型模式的定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象 。
原型模式的功能: 1、通过克隆来创建新的对象实例; 2、为克隆出来的新对象实例复制原型实例属性值;
克隆:无论是自己实现克隆方法,还是采用C#提供的克隆方法,都存在一个浅度克隆和深度克隆的问题:
1、浅度克隆:只负责克隆按值传递的数据(比如基本数据类型、String类型);
2、深度克隆:除了浅度克隆要克隆的值外,还负责克隆引用类型的数据,基本上就是被克隆实例所有的属性数据都会被克隆出来;
序号 | 原型模式的优点 | 原型模式的缺点 |
1 | 对客户端隐藏具体的实现类型 (即:原型模式的客户端只知道原型接口类型,并不知道具体的实现类型, 从而减少了客户端对具体实现类型的依赖) | 每个原型的子类都必须实现克隆操作,尤其在包含引用类型的对象时,克隆方法会比较麻烦,必须要能够递归地让所有相关对象都要正确地实现克隆 |
2 | 在运行时动态改变具体的实现类型 (即:原型模式可以在运行期间,由客户来注册符合原型接口的实现类型,也可以动态改变具体的实现类型,看起来接口没有任何变化,但其实运行的已经是另外一个类实例了【因为克隆一个原型就类似于实例化一个类】) |
何时选用原型模式?
1、如果一个系统想要独立于它想要使用的对象时【让系统只面向接口编程,在系统需要新对象时可以通过克隆原型获取】;
2、如果需要实例化的类是在运行时动态指定的,可通过克隆原型类得到想要的实例。
二、原型模式示例
业务需求:比如我们有一个订单处理功能,需要保存订单业务(在这个业务功能中,每当订单的预订数量超过1000的时候,就需要将订单拆分为两份订单保存;如果拆成了两份订单后,数量还是超过1000,则继续拆分,直到每份订单的数量不超过1000);且这个订单类型会分为两种(一种是个人订单;一种是公司订单),无论何种订单类型都需要按照业务规则处理。
2.1、不使用模式的示例
既然有两种订单类型,且都要实现保存订单相关业务的通用功能,那么我们可以定义一个接口来声明这些功能行为,然后在定义具体的类分别实现即可:
1、定义订单接口
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、分别定义个人订单与企业订单类来实现接口定义的功能行为
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
}
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、现在的中心任务就是要实现《保存订单》的业务方法
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、编写客户端测试
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、定义订单接口规范产品功能行为
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一个自己的对象实例,然后再把自己实例的数据取出来赋值到新对象实例中去】如下所示:
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
}
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、创建一个类构建通用的保存订单业务方法且不依赖具体的实例对象、方法
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、客户端测试原型模式
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、自己手动实现克隆方法
原型实例与克隆出来的实例,本质上是不同的实例(克隆完成后,它们之间是没有关联,互不影响的,也就是说它们所指向不同的内存空间)如下所示:
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、创建接口
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#克隆接口实现功能
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、客户端调用测试
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、定义产品接口规范产品行为功能
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、定义一个产品对象,继承产品接口并实现克隆功能
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、订单对象的添加产品对象属性
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、客户端测试
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】
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、实现个人订单对象添加产品属性内容
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、客户端测试
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、定义原型接口规范行为功能
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、定义类对象原型继承接口实现具体功能
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
}
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、实现原型管理器
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、客户端使用原型管理器
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、运行结果:
三、项目源码工程
kafeiweimei/Learning_DesignPattern: 这是一个关于C#语言编写的基础设计模式项目工程,方便学习理解常见的26种设计模式https://github.com/kafeiweimei/Learning_DesignPattern