当前位置: 首页 > article >正文

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

一、基础概念

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

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

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

相关文章:

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

一、基础概念 原型模式的本质是【克隆生成对象】&#xff1b; 原型模式的定义&#xff1a;用原型实例指定创建对象的种类&#xff0c;并通过拷贝这些原型创建新的对象 。 原型模式的功能&#xff1a; 1、通过克隆来创建新的对象实例&#xff1b; 2、为克隆出来的新对象实例复制…...

疗愈服务预约小程序源码介绍

基于ThinkPHP、FastAdmin和UniApp开发的疗愈服务预约小程序源码&#xff0c;这款小程序在功能设计和用户体验上都表现出色&#xff0c;为疗愈行业提供了一种全新的服务模式。 该小程序源码采用了ThinkPHP作为后端框架&#xff0c;保证了系统的稳定性和高效性。同时&#xff0c…...

如何通过外网访问内网?对比5个简单的局域网让互联网连接方案

在实际应用中&#xff0c;常常需要从外网访问内网资源&#xff0c;如远程办公访问公司内部服务器、在家访问家庭网络中的设备等。又或者在本地内网搭建的项目应用需要提供互联网服务。以下介绍几种常见的外网访问内网、内网提供公网连接实现方法参考。 一、公网IP路由器端口映…...

Linux 服务器静态 IP 配置初始化指南

✅ 第一步&#xff1a;确认网络管理方式 运行以下命令判断系统使用的网络管理服务&#xff1a; # 检查 NetworkManager 是否活跃 systemctl is-active NetworkManager# 检查 network&#xff08;旧服务&#xff09;是否活跃 systemctl is-active network或者检查配置路径&…...

【随笔】Google学术:but your computer or network may be sending automated queries.

文章目录 一、问题复述二、问题原因三、解决 前提&#xff1a;你的xxx是自己做的&#xff0c;你自己可以管理&#xff0c;而不是用的那些劣质✈场。 一、问题复述 &#x1f7e2;如下图所示&#xff1a;可以打开谷歌学术&#xff0c;但是一搜索就是这个界面。 二、问题原因 …...

长事务:数据库中的“隐形炸弹“——金仓数据库运维避坑指南

引言&#xff1a;凌晨三点的告警 "张工&#xff01;生产库又告警了&#xff01;"凌晨三点的电话铃声总是格外刺耳。运维团队发现数据库频繁进入单用户模式&#xff0c;排查发现某核心表的年龄值&#xff08;Age&#xff09;已突破20亿大关。经过一夜奋战&#xff0c…...

ubuntu nobel + qt5.15.2 设置qss语法识别正确

问题展示 解决步骤 首选项里面的高亮怎么编辑选择都没用。如果已经有generic-highlighter和css.xml&#xff0c;直接修改css.xml文件最直接&#xff01; 在generic-highlighter目录下找到css.xml文件&#xff0c;位置是&#xff1a;/opt/Qt/Tools/QtCreator/share/qtcreator/…...

优化01-统计信息

Oracle 的统计信息是数据库优化器生成高效执行计划的核心依据。它记录了数据库对象&#xff08;如表、索引、列等&#xff09;的元数据信息&#xff0c;帮助优化器评估查询成本并选择最优执行路径。以下是关于 Oracle 统计信息的详细介绍&#xff1a; 一、统计信息的分类 表统…...

Unity-Socket通信实例详解

今天我们来讲解socket通信。 首先我们需要知道什么是socket通信&#xff1a; Socket本质上就是一个个进程之间网络通信的基础&#xff0c;每一个Socket由IP端口组成&#xff0c;熟悉计网的同学应该知道IP主要是应用于IP协议而端口主要应用于TCP协议&#xff0c;这也证明了Sock…...

MATLAB仿真定点数转浮点数(对比VIVADO定点转浮点)

MATLAB仿真定点数转浮点数 定点数可设置位宽&#xff0c;小数位宽&#xff1b;浮点数是单精度浮点数 对比VIVADO定点转浮点 目录 前言 一、定点数 二、浮点数 三、定点数转浮点数 四、函数代码 总结 前言 在FPGA上实现算法时&#xff0c;相比MATLAB实现往往需要更长的开发…...

配置Jupyter Notebook环境及Token认证(Linux服务器)

配置Jupyter Notebook环境及Token认证&#xff08;Linux服务器&#xff09; 背景 在Ubuntu 18.04.6 LTS服务器&#xff08;IP: 39.105.167.2&#xff09;上&#xff0c;基于虚拟环境pytorch_env&#xff0c;通过Mac终端&#xff08;SSH&#xff09;配置Jupyter Notebook环境&…...

【计算机网络】Cookie、Session、Token之间有什么区别?

大家在日常使用浏览器时可能会遇到&#xff1a;是否清理Cookie&#xff1f;这个问题。 那么什么是Cookie呢&#xff1f;与此相关的还有Session、Token这些。这两个又是什么呢&#xff1f; 本文将对这三个进行讲解区分&#xff0c;如果对小伙伴有帮助的话&#xff0c;也请点赞、…...

SpringCloud服务拆分:Nacos服务注册中心 + LoadBalancer服务负载均衡使用

SpringCloud中Nacos服务注册中心 LoadBalancer服务负载均衡使用 前言Nacos工作流程nacos安装docker安装window安装 运行nacos微服务集成nacos高级特性1.服务集群配置方法效果图模拟服务实例宕机 2.权重配置3.环境隔离 如何启动集群节点本地启动多个节点方法 LoadBalancer集成L…...

Spring AI 集成 DeepSeek V3 模型开发指南

Spring AI 集成 DeepSeek V3 模型开发指南 前言 在人工智能飞速发展的当下&#xff0c;大语言模型不断推陈出新&#xff0c;DeepSeek AI 推出的开源 DeepSeek V3 模型凭借其卓越的推理和问题解决能力备受瞩目。与此同时&#xff0c;Spring AI 作为一个强大的框架&#xff0c;…...

Apache Doris 使用指南:从入门到生产实践

目录 一、Doris 核心概念 1.1 架构组成 1.2 数据模型 二、Doris 部署方式 2.1 单机部署&#xff08;测试环境&#xff09; 2.2 集群部署&#xff08;生产环境&#xff09; 三、数据操作指南 3.1 数据库与表管理 3.2 数据导入方式 3.2.1 批量导入 3.2.2 实时导入 3.…...

26届秋招收割offer指南

26届暑期实习已经陆续启动&#xff0c;这也意味着对于26届的同学们来说&#xff0c;“找工作”已经提上了日程。为了帮助大家更好地准备暑期实习和秋招&#xff0c;本期主要从时间线、学习路线、核心知识点及投递几方面给大家介绍&#xff0c;希望能为大家提供一些实用的建议和…...

拷贝多个Excel单元格区域为图片并粘贴到Word

Excel工作表Sheet1中有两个报表&#xff0c;相应单元格区域分别定义名称为Report1和Report2&#xff0c;如下图所示。 现在需要将图片拷贝图片粘贴到新建的Word文档中。 示例代码如下。 Sub Demo()Dim oWordApp As ObjectDim ws As Worksheet: Set ws ThisWorkbook.Sheets(&…...

Kafka消息队列之 【消费者分组】 详解

消费者分组(Consumer Group)是 Kafka 提供的一种强大的消息消费机制,它允许多个消费者协同工作,共同消费一个或多个主题的消息,从而实现高吞吐量、可扩展性和容错性。 基本概念 消费者分组:一组消费者实例的集合,这些消费者实例共同订阅一个或多个主题,并通过分组来协调…...

【Bluedroid】蓝牙 SDP(服务发现协议)模块代码解析与流程梳理

本文深入剖析Bluedroid蓝牙协议栈中 SDP&#xff08;服务发现协议&#xff09;服务记录的全生命周期管理流程&#xff0c;涵盖初始化、记录创建、服务搜索、记录删除等核心环节。通过解析代码逻辑与数据结构&#xff0c;揭示各模块间的协作机制&#xff0c;包括线程安全设计、回…...

中国自动驾驶研发解决方案,第一!

4月28日&#xff0c;IDC《中国汽车云市场(2024下半年)跟踪》报告发布&#xff0c;2024下半年中国汽车云市场整体规模达到65.1亿元人民币&#xff0c;同比增长27.4%。IDC认为&#xff0c;自动驾驶技术深化与生成式AI的发展将为汽车云打开新的成长天花板&#xff0c;推动云计算在…...

Kubernetes(k8s)学习笔记(四)--入门基本操作

本文通过kubernetes部署tomcat集群&#xff0c;来学习和掌握kubernetes的一些入门基本操作 前提条件 1.各个节点处于Ready状态&#xff1b; 2.配置好docker镜像库(否则会出现ImagePullBackOff等一些问题)&#xff1b; 3.网络配置正常(否则即使应用发布没问题&#xff0c;浏…...

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

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

【基础复习笔记】计算机视觉

目录 一、计算机视觉基础 1. 卷积神经网络原理 2. 目标检测系列 二、算法与模型实现 1. 在PyTorch/TensorFlow中实现自定义损失函数或网络层的步骤是什么&#xff1f; 2. 如何设计一个轻量级模型用于移动端的人脸识别&#xff1f; 3. 描述使用过的一种注意力机制&#…...

基于 GO 语言的 Ebyte 勒索软件——简要分析

一种新的勒索软件变种,采用Go 语言编写,使用ChaCha20进行加密,并使用ECIES进行安全密钥传输,加密用户数据并修改系统壁纸。其开发者EvilByteCode曾开发过多种攻击性安全工具,现已在 GitHub 上公开 EByte 勒索软件。尽管该勒索软件声称仅用于教育目的,但滥用可能会导致严重…...

0基础 | STM32 | STM32F103C8T6开发板 | 项目开发

注&#xff1a;本专题系列基于该开发板进行&#xff0c;会分享源代码 F103C8T6核心板链接&#xff1a; https://pan.baidu.com/s/1EJOlrTcProNQQhdTT_ayUQ 提取码&#xff1a;8c1w 图 STM32F103C8T6开发板 1、黑色制版工艺、漂亮、高品质 2、入门级配置STM32芯片(SEM32F103…...

南京大学OpenHarmony技术俱乐部正式揭牌 仓颉编程语言引领生态创新

2025年4月24日&#xff0c;由OpenAtom OpenHarmony&#xff08;以下简称“OpenHarmony”&#xff09;项目群技术指导委员会与南京大学软件学院共同举办的“南京大学OpenHarmony技术俱乐部成立大会暨基础软件与生态应用论坛”在南京大学仙林校区召开。 大会聚焦国产自主编程语言…...

主场景 工具栏 植物卡牌的渲染

前置知识&#xff1a;使用easyx图形库 1.IMAGE内存变量存储的是一张位图(图像)&#xff0c;存储了像素数据(颜色&#xff0c;尺寸等) 2.loadimage(&变量名&#xff0c;"加载的文件路径")表示从文件中加载图像到变量中 3. saveimage("文件路径", &变…...

计算机网络:深入分析三层交换机硬件转发表生成过程

三层交换机的MAC地址转发表生成过程结合了二层交换和三层路由的特性,具体可分为以下步骤: 一、二层MAC地址表学习(基础转发层) 初始状态 交换机启动时,MAC地址表为空,处于学习阶段。 数据帧接收与源MAC学习 当主机A发送数据帧到主机B时,交换机会检查数据帧的源MAC地址。…...

Java三大基本特征之多态

多态&#xff08;Polymorphism&#xff09;是面向对象编程&#xff08;OOP&#xff09;的三大特性之一&#xff08;另外两个是 封装 和 继承&#xff09;&#xff0c;它允许 同一个行为具有不同的表现形式。在 Java 中&#xff0c;多态主要通过 方法重写&#xff08;Override&a…...

OpenCV 基于生物视觉模型的工具------模拟人眼视网膜的生物视觉机制类cv::bioinspired::Retina

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::bioinspired::Retina 是 OpenCV 中用于仿生视觉处理的一个类&#xff0c;它基于生物视觉模型进行图像预处理。该算法特别适用于动态范围调整…...