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

一、基础概念

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

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

原型模式的功能: 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 Learnhttps://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、运行结果:

三、项目源码工程

kafeiweimei/Learning_DesignPattern: 这是一个关于C#语言编写的基础设计模式项目工程,方便学习理解常见的26种设计模式https://github.com/kafeiweimei/Learning_DesignPattern

相关推荐
西岸行者2 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意2 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码2 天前
嵌入式学习路线
学习
毛小茛2 天前
计算机系统概论——校验码
学习
babe小鑫2 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms2 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下2 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。2 天前
2026.2.25监控学习
学习
im_AMBER2 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J2 天前
从“Hello World“ 开始 C++
c语言·c++·学习