学习设计模式《八》——原型模式

一、基础概念

        原型模式的本质是【克隆生成对象】;

        原型模式的定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象

        原型模式的功能: 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 Learnhttps://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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/82147.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

olmOCR - PDF文档处理工具包

文章目录 一、关于 olmOCR相关资源包含内容团队 二、安装三、本地使用示例查看结果多节点/集群使用管道完整文档 一、关于 olmOCR olmOCR 是用于训练语言模型处理PDF文档的工具包&#xff0c;支持大规模PDF文本解析和转换。 相关资源 源码&#xff1a;https://github.com/all…

Android开发补充内容

Android开发补充内容 fragment通信生命周期 Okhttp基本使用websocket Retrofit基本使用 RxJava基本使用定时任务 Hilt基本使用进阶使用例子 组件库Material ComponentsJetpack Compose fragment 通信 fragment于activity通信的一种原生方法是使用Bundle&#xff1a; Bundle …

隐私计算框架FATE二次开发心得整理(工业场景实践)

文章目录 版本介绍隐私计算介绍前言FATE架构总体架构FateBoard架构前端架构后端架构 FateClient架构创建DAG方式DAG生成任务管理python SDK方式 FateFlow架构Eggroll架构FATE算法架构Cpn层FATE ML层 组件新增流程新增组件流程新增算法流程 版本介绍 WeBank的FATE开源版本 2.2.…

AI驱动的制造工艺:系统化探索与创新

DeepSeek 技术全景 在当今 AI 技术蓬勃发展的时代,DeepSeek 已成为该领域中一颗耀眼的明星。自 2023 年 7 月 17 日成立以来,这家由知名私募巨头幻方量化孕育而生的公司,迅速在 AI 领域崭露头角 。DeepSeek 的目标是开发顶尖的大语言模型(LLM),并利用数据蒸馏技术打造更精…

【嵌入式开发-LCD】

嵌入式开发-LCD ■ LCD简介 ■ LCD简介

java反射(2)

package 反射;import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays;public class demo {public static void main(String[] args) throws Exception {// 通过类的全限定名获取对应的 Class 对象…

使用 Cesium 构建 3D 地图应用的实践

CesiumJS 是一个功能强大的开源 JavaScript 库&#xff0c;能够帮助开发者快速构建高性能、高精度的 3D 地球和地图应用 。本文将介绍如何使用 Cesium 构建一个基本的 3D 地图应用&#xff0c;并加载自定义的 3D Tiles 模型。 初始化 Cesium Viewer 首先&#xff0c;在 Vue 的…

结合Splash与Scrapy:高效爬取动态JavaScript网站

在当今的Web开发中&#xff0c;JavaScript的广泛应用使得许多网站的内容无法通过传统的请求-响应模式直接获取。为了解决这个问题&#xff0c;Scrapy开发者经常需要集成像Splash这样的JavaScript渲染引擎。本文将详细介绍Splash JS引擎的工作原理&#xff0c;并探讨如何将其与S…

企业级可观测性实现:OpenObserve云原生平台的本地化部署与远程访问解析

文章目录 前言1. 安装Docker2. 创建并启动OpenObserve容器3. 本地访问测试4. 公网访问本地部署的OpenObserve4.1 内网穿透工具安装4.2 创建公网地址 5. 配置固定公网地址 前言 嘿&#xff0c;各位小伙伴们&#xff0c;今天要给大家揭秘一个在云原生领域里横扫千军的秘密法宝—…

将本地项目提交到新建的git仓库

方式一: # 登录git&#xff0c;新建git仓库和指定的分支&#xff0c;如master、dev# 下载代码&#xff0c;默认下载master分支 git clone http://10.*.*.67/performance_library/pfme-*.git # 切换到想要提交代码的dev分支 git checkout dev# 添加想要提交的文件 git add .#…

.NET平台用C#在PDF中创建可交互的表单域(Form Field)

在日常办公系统开发中&#xff0c;涉及 PDF 处理相关的开发时&#xff0c;生成可填写的 PDF 表单是一种常见需求&#xff0c;例如员工信息登记表、用户注册表、问卷调查或协议确认页等。与静态 PDF 不同&#xff0c;带有**表单域&#xff08;Form Field&#xff09;**的文档支持…

在macOS上安装windows系统

使用Boot Camp 1. 准备工作&#xff1a;确认Mac满足Boot Camp系统要求&#xff0c;准备好Windows安装光盘或ISO映像文件&#xff0c;以及一个至少8GB的空白USB闪存驱动器用于保存驱动程序。 2. 打开Boot Camp助理&#xff1a;在“应用程序”文件夹的“实用工具”中找到“Boot…

683SJBH基于J2EE的广州旅游管理系统

第1章  绪论 课题背景 自互联网internet成为一种革命性的大众媒体以来&#xff0c;其发展速度之快令人惊叹。而作为世界最大朝阳产业的旅游&#xff0c;当它与电子商务这一新兴模式相结合时&#xff0c;其潜藏的商业价值表露无遗。根据CNN&#xff08;美国有线电视新闻网&…

前端面试每日三题 - Day 27

这是我为准备前端/全栈开发工程师面试整理的第27天每日三题练习&#xff0c;涵盖了&#xff1a; CSS选择器的优先级与权重计算机制Angular中的依赖注入&#xff08;Dependency Injection&#xff09;机制设计一个支持实时协作编辑&#xff08;如Google Docs&#xff09;的前端…

PostgreSQL数据库操作SQL

数据库操作SQL 创建 创建数据库 create database db_test;创建并指定相关参数 with owner : 所有者encoding : 编码connection limit &#xff1a;连接限制 create database db_test1 with owner postgresencoding utf-8connection limit 100;修改 修改数据库名称 renam…

JSP HTTP 状态码详解

JSP HTTP 状态码详解 引言 HTTP 状态码是 HTTP 协议的一部分,用于表示客户端与服务器之间请求与响应的状态。在 JavaServer Pages (JSP) 技术中,HTTP 状态码同样扮演着重要的角色。本文将详细解析 JSP 中的 HTTP 状态码,帮助开发者更好地理解和应用这些状态码。 HTTP 状态…

文件一键解密软件工具(支持pdf、word、excel、ppt、rar、zip格式文件)

一键解密解锁神器支持解密pdf、doc、docx、xls、xlsx、ppt、pptx、rar、zip格式文件&#xff0c;Excel表格、Word文档、PPT演示、RAR、ZIP压缩包、PDF文档一键轻松解密&#xff01;简单/高效/安全。这款软件由密码帝官方提供&#xff0c;确保了其合法性和安全性&#xff0c;用户…

Banana Pi BPI-CM6 是一款八核 RISC-V 模块,兼容 Raspberry Pi CM 载板

Banana Pi BPI-CM6 是一款 SpacemIT K1 八核 RISC-V 系统级模块&#xff0c;遵循 Raspberry Pi CM5 的设计&#xff0c;并提供高达 16GB LPDDR4 RAM、高达 128GB eMMC 闪存、千兆以太网控制器和 WiFi 6 蓝牙 5.2 模块。 BPI-CM6 虽然与 Raspberry Pi CM5 基本兼容&#xff0c…

【项目篇之统一硬盘操作】仿照RabbitMQ模拟实现消息队列

统一硬盘操作 创建出实例封装交换机的操作封装队列的操作封装绑定的操作封装消息的操作总的完整代码&#xff1a; 我们之前已经使用了数据库去管理交换机&#xff0c;绑定&#xff0c;队列 还使用了数据文件去管理消息 此时我们就搞一个类去把上述两个部分都整合在一起&#…

快速上手SpringBoot开发指南

文章目录 1. 项目整体架构2. SpringBoot核心注解详解2.1 应用程序入口注解SpringBootApplication 2.2 控制器层注解RestControllerRequestMappingPostMappingRequestBody 2.3 服务层注解ServiceAutowired 2.4 数据访问层注解Repository 2.5 实体类注解JPA相关注解Lombok注解 3.…