设计模式深耕(原型模式( Prototype Pattern ))

原型模式( Prototype Pattern )

1 原型模式的定义

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

image

Prototype:声明一个拷贝自身的接口。
ConcretePrototype:实现一个拷贝自身的操作。
Client:让一个原型拷贝自身从而创建一个新的对象。

原型模式的核心是一个 Clone() 方法,通过该方法进行对象的拷贝,net 提供了一个 ICloneable 的接口来标示这个对象是可拷贝的,为什么说是“标示”呢?首先 ICloneable 是一个接口并无具体实现,直接查看方法的注释并反编译看下源码,在 net 中具有这个标记的对象才有可能被拷贝。那怎么才能从“有可能被拷贝”转换为“可以被拷贝”呢?方法是实现 ICloneable 接口的 Clone() 方法。这种不通过 new 关键字来产生一个对象,而是通过对象复制来实现的模式就叫做原型模式。

using System;
using System.Runtime.CompilerServices;

namespace System
{
	/// <summary>Supports cloning, which creates a new instance of a class with the same value as an existing instance.</summary>
	[NullableContext(1)]
	public interface ICloneable
	{
		/// <summary>Creates a new object that is a copy of the current instance.</summary>
		/// <returns>A new object that is a copy of this instance.</returns>
		object Clone();
	}
}

2 原型模式的应用

2.1 原型模式的优点

  • 性能优良
    原型模式是在内存二进制流的拷贝,要比直接 new 一个对象性能好,特别是要在一个循环体内产生大量的对象时,原型模式可以更好的体现其优点。
  • 逃避构造函数的约束
    这既是它的优点也是缺点,直接在内存中拷贝,构造函数时不会执行,优点就是减少了约束,缺点也是减少了约束,需要从实际应用时考虑。

2.2 原型模式的使用场景

  • 资源优化场景
    类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
  • 性能和安全要求的场景
    通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
  • 一个对象多个修改者的场景
    一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。

3 原型模式的注意事项

3.1 构造函数不会被执行

一个继承了 ICloneable 接口并实现了 Clone() 方法的类,有一个无参数构造或有参构造函数,通过 new 关键字产生了一个对象 X ,然后再通过 X.Clone() 方式拷贝了一个新对象 T ,那么在对象拷贝时构造函数是不会被执行的。咱们通过一个代码样例来验证下:

可拷贝类

    public class PrototypeConstructorSample : ICloneable
    {
        public PrototypeConstructorSample()
        {
            Console.WriteLine("构造函数被执行了..");
        }

        public object Clone()
        {
            return MemberwiseClone();
        }
    }

在 net 中 ICloneableClone()MemberwiseClone() 搭伙使用。

MemberwiseClone():创建一个当前 Object 对象的浅度副本。
MemberwiseClone() 创建一个浅度副本,创建一个新对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用类型,则复制引用但不复制引用的对象;因此,原始对象及其副本引用同一对象,也就是在内存上指向同一块内存。

场景执行程序

    class Program
    {
        static void Main(string[] args)
        {
            var sample1 = new PrototypeConstructorSample();
            var sample2 = sample1.Clone();
        }
    }

运行结果:

构造函数被执行了..

看运行结果输出只有一次证明确实通过 Clone() 拷贝的没有执行构造函数,这点从原理来讲也是可以讲得通的, Object 类的 Clone 方法的原理是从内存中(具体的说就是堆内存)以二进制流的方法进行拷贝,重写分配一个内存块,那构造函数没有被执行也是非常正确的了。

3.2 浅拷贝和深拷贝

在解释什么是浅拷贝和深拷贝之前,我们先来看个样例。

可拷贝类

    public class PrototypeSample2 : ICloneable
    {
        /// <summary>
        /// 集合值
        /// </summary>
        public List<string> ListValue { get; set; }

        /// <summary>
        /// 输出值
        /// </summary>
        public void ConsoleValue()
        {
            Console.WriteLine("---------Start");
            foreach (var item in ListValue)
            {
                Console.WriteLine(item);
            }
            Console.WriteLine("---------End");
        }

        public object Clone()
        {
            return MemberwiseClone();
        }
    }

场景执行程序

    class Program
    {
        static void Main(string[] args)
        {
            // new 一个对象
            PrototypeSample2 sample1 = new PrototypeSample2();
            // 给对象中属性赋值
            sample1.ListValue = new List<string>() { "A" };
            // 通过 Clone() 拷贝一个新对象并显性转换
            PrototypeSample2 sample2 = (PrototypeSample2)sample1.Clone();
            // 再次赋值
            sample2.ListValue.Add("B");
            // 输出 sample1 属性 ListValue 的值
            sample1.ConsoleValue();
        }
    }

运行结果:

---------Start
A
B
---------End

看运行结果都会想,怎么 sample1 会有 B 呢?是因为 net 做了一个偷懒的拷贝动作,Object 类提供了 Clone() 方法的实现方法 MemberwiseClone() 只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,这种拷贝就叫做浅拷贝。

使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝:

  1. 类的成员变量,而不是方法内变量;
  2. 必须是一个可变的引用对象,而不是一个原始类型或不可变对象。

ps:String 类型是一种特殊的引用类型(编译器做特殊处理了),在 net 原型模式使用时把其当成值类型,与 int、long、char 一致的处理即可。

现在咱们已经了解了浅拷贝,可是浅拷贝是具有风险的,那怎么才能深入的拷贝呢?ok,咱们修改下代码,如下:

拷贝操作

    /// <summary>
    /// 拷贝操作(深拷贝、浅拷贝)
    /// </summary>
    public static class Cloneable
    {
        /// <summary>
        /// 深拷贝(二进制)
        /// </summary>
        /// <param name="value">待拷贝的对象</param>
        public static T Clone2<T>(this T value)
        {
            if (!typeof(T).IsSerializable)
                throw new ArgumentException("值类型必须是可序列化");
            if (value == null)
                return default(T);
            // 声明以二进制格式序列化和反序列化的对象
            var formatter = new BinaryFormatter();
            // 声明一个内存流
            var stream = new MemoryStream();
            using (stream)
            {
                // 将对象序列化到指定流
                formatter.Serialize(stream, value);
                // 写入
                stream.Seek(0, SeekOrigin.Begin);
                // 流反序列化
                return (T)formatter.Deserialize(stream);
            }
        }
    }

可拷贝类

    [Serializable]// 支持序列化/反序列化
    public class PrototypeSample2
    {
        /// <summary>
        /// 集合值
        /// </summary>
        public List<string> ListValue { get; set; }

        /// <summary>
        /// 输出值
        /// </summary>
        public void ConsoleValue()
        {
            Console.WriteLine("---------Start");
            if (ListValue != null)
            {
                foreach (var item in ListValue)
                {
                    Console.WriteLine(item);
                }
            }
            Console.WriteLine("---------End");
        }
    }

场景执行程序

    class Program
    {
        static void Main(string[] args)
        {
            // new 一个对象
            PrototypeSample2 sample1 = new PrototypeSample2();
            // 给对象中属性赋值
            sample1.ListValue = new List<string>() { "A" };
            // 通过 Clone2() 拷贝一个新对象并显性转换
            PrototypeSample2 sample2 = sample1.Clone2();
            // 再次赋值
            sample2.ListValue.Add("B");
            // 输出 sample1 属性 ListValue 的值
            sample1.ConsoleValue();
        }
    }

运行结果:

---------Start
B
---------End

在浅拷贝的代码基础之上做了如下修改:

  1. 增加一个自定义以二进制拷贝方式的拷贝操作的工具类。
  2. “可拷贝类”去掉“ ICloneable ”的继承,删除了 Clone() 方法的实现,增加了支持序列化/反序列化 [Serializable] 的标识。
  3. “场景执行程序” 对 PrototypeSample2 的拷贝使用自定义工具类的 Clone2() 的二进制拷贝方法。

该工具类的拷贝方法就实现了完全的拷贝,两个对象之间没有任何的瓜葛,你修改你的,我修改我的,不相互影响,在内存中也是两个块内存,这种拷贝就叫做深拷贝。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值