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

一、基础概念

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

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

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

相关推荐
LVerrrr20 分钟前
Missashe考研日记-day32
学习·考研
Hxyle21 分钟前
c++设计模式
开发语言·c++·设计模式
摘星编程27 分钟前
并发设计模式实战系列(17):信号量(Semaphore)
设计模式·并发编程
琢磨先生David1 小时前
Java 企业级开发设计模式全解析
java·设计模式
不当菜虚困2 小时前
JAVA设计模式——(十一)建造者模式(Builder Pattern)
java·设计模式·建造者模式
吃货界的硬件攻城狮2 小时前
【STM32 学习笔记】ADC数模转换器
笔记·stm32·单片机·学习
codefly-xtl2 小时前
责任链设计模式
java·开发语言·设计模式
菜鸟破茧计划2 小时前
C++ 算法学习之旅:从入门到精通的秘籍
c++·学习·算法
海尔辛2 小时前
学习黑客 MAC 地址深入了解
学习·macos·php
喜欢吃燃面2 小时前
C++:扫雷游戏
c语言·c++·学习