本文作者:小黑黑

[设计模式]创建型模式之原型模式

小黑黑 1年前 ( 2019-02-19 ) 1033 抢沙发
[设计模式]创建型模式之原型模式摘要: 一、引言        在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这...

一、引言

        在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样的实例时,如果我们使用new去创建这样的实例,这样就会增加创建类的复杂度和创建过程与客户程序代码的耦合度。如果我们采用工厂模式去创建这样实例的话,随着产品类的不断增加,导致子类的数量不断增加,反而增加了系统的复杂度,所以在这里使用工厂模式来封装类的创建过程并不合适,原型模式却很好的解决了这个问题,因为类的实例是相同的,当我们需要多个相同的实例时,没必要每次都去new一个类的对象,我们只需要创建一个类的实例,如果后面需要更多相同的实例话,可以通过对原来实例的拷贝来创建。

二、动机

        在软件系统中,经常面临着“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口。

        如何应对这种变化?如何向“客户程序(使用这些对象的程序)”隔离出“这些易变对象”,从而使得“依赖这些易变对象的客户程序”不随需求改变而改变?

三、意图

        使用原型实例制定创建对象的种类,然后通过拷贝这些原型来创建新的对象   --《设计模式》  GoF

四、结构

RAYNI%LJX0}J%JCX2PRWYNJ.png


五、原型模式的具体实现

        西游记,大家应该都看过,孙悟空只需拔根猴毛,轻轻一吹,就可以变成许多个相同的自己,我们就拿孙悟空举例。

    class Program
    {
        static void Main(string[] args)
        {

            //孙悟空原型
            MonkeyKingPrototype monkeyKingPrototype = new ConcretePrototype(1, "孙悟空");

            //克隆出来的孙悟空
            MonkeyKingPrototype cloneMonkeyKing = monkeyKingPrototype.Clone() as ConcretePrototype;

            Console.WriteLine($"Id:{monkeyKingPrototype.Id}, name:{monkeyKingPrototype.Name}");
            Console.WriteLine($"Id:{cloneMonkeyKing.Id}, name:{cloneMonkeyKing.Name}");

            Console.Read();
        }
    }

    /// <summary>
    /// 孙悟空原型类
    /// </summary>
    public abstract class MonkeyKingPrototype
    {
        public int Id { get; set; }

        public string Name { get; set; }

        protected MonkeyKingPrototype(int id, string name)
        {
            this.Id = id;
            this.Name = name;
        }

        /// <summary>
        /// 克隆方法
        /// </summary>
        public abstract MonkeyKingPrototype Clone();
    }

    /// <summary>
    /// 创建具体原型
    /// </summary>
    public class ConcretePrototype : MonkeyKingPrototype
    {
        public ConcretePrototype(int id, string name) : base(id, name) { }

        /// <summary>
        /// 克隆方法
        /// </summary>
        public override MonkeyKingPrototype Clone()
        {
            return (MonkeyKingPrototype)this.MemberwiseClone();
        }
    }

        运行程序,我们可以看到克隆出来的孙悟空和孙悟空原型id和name都是相同的。

O9K20_8A6GW`MK]KFRLX~3U.png

        当我们改变孙悟空的原型id和name时,克隆出来的孙悟空的id和name并没有改变。

   monkeyKingPrototype.Id = 2;
   monkeyKingPrototype.Name = "齐天大圣";

   Console.WriteLine($"Id:{monkeyKingPrototype.Id}, name:{monkeyKingPrototype.Name}");
   Console.WriteLine($"Id:{cloneMonkeyKing.Id}, name:{cloneMonkeyKing.Name}");

        上面代码实现的是浅拷贝的方式,浅拷贝是指如果字段是值类型,则对该字段执行逐位复制,如果字段是引用类型,则只复制引用但不复制引用对象;因此,元素对象及其副本对象应用同一个对象。具体什么意思呢?比如孙悟空在菩提老祖那学习法术时分了班级Class,那么对孙悟空进行克隆时,复制的是班级的引用地址,而不包括集体的班级对象的字段信息。我们将代码修改为这样:

    class Program
    {
        static void Main(string[] args)
        {

            //孙悟空原型
            MonkeyKingPrototype monkeyKingPrototype = new ConcretePrototype(1, "孙悟空", new Class() { ClassId = 1, ClassName = "1班" });

            //克隆出来的孙悟空
            MonkeyKingPrototype cloneMonkeyKing = monkeyKingPrototype.Clone() as ConcretePrototype;

            monkeyKingPrototype.Class.ClassId = 2;
            monkeyKingPrototype.Class.ClassName = "2班";

            Console.WriteLine($"孙悟空原型所在的班级:id:{monkeyKingPrototype.Class.ClassId}, name:{monkeyKingPrototype.Class.ClassName}");
            Console.WriteLine($"克隆孙悟空所在的班级:id:{cloneMonkeyKing.Class.ClassId}, name:{cloneMonkeyKing.Class.ClassName}");


            Console.Read();
        }
    }
    
    /// <summary>
    /// 孙悟空原型类
    /// </summary>
    public abstract class MonkeyKingPrototype
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public Class Class { get; set; }

        protected MonkeyKingPrototype(int id, string name, Class c)
        {
            this.Id = id;
            this.Name = name;
            this.Class = c;
        }

        /// <summary>
        /// 克隆方法
        /// </summary>
        public abstract MonkeyKingPrototype Clone();
    }

    /// <summary>
    /// 创建具体原型
    /// </summary>
    public class ConcretePrototype : MonkeyKingPrototype
    {
        public ConcretePrototype(int id, string name, Class c) : base(id, name, c) { }

        /// <summary>
        /// 克隆方法
        /// </summary>
        public override MonkeyKingPrototype Clone()
        {
            return (MonkeyKingPrototype)this.MemberwiseClone();
        }
    }

    /// <summary>
    /// 所在班级
    /// </summary>
    public class Class
    {
        /// <summary>
        /// 班级ID
        /// </summary>
        public int ClassId { get; set; }

        /// <summary>
        /// 班级名称
        /// </summary>
        public string ClassName { get; set; }
    }

        运行程序,我们发现,当我们修改class里面的属性值时,孙悟空原型和克隆的孙悟空他们的班级ID和班级名称都发生了改变,这是为什么呢,因为我们克隆孙悟空原型时,class属性为引用类型,我们克隆的是引用类型的地址,孙悟空原型和克隆的孙悟空的班级都指向同一个class,所以改变id和name时,两个对象里面的属性都发生了改变。

KZY@HOHA[W){ZUEPGHPJX67.png

        如果我们想要实现深拷贝该怎么办呢,一是我们可以再创建具体原型时,手动为引用类型赋值。二是我们可以使用序列化的方式来实现深拷贝。首先使用手动赋值的方式:

    //monkeyKingPrototype.Id = 2;
    //monkeyKingPrototype.Name = "齐天大圣";

    //手动为class里面的属性赋值
    monkeyKingPrototype.Class = new Class()
    {
         ClassId = 2,
         ClassName = "2班"
    };

        使用序列化的方式实现深拷贝:

    class Program
    {
        static void Main(string[] args)
        {

            //孙悟空原型
            MonkeyKingPrototype monkeyKingPrototype = new ConcretePrototype(1, "孙悟空", new Class() { ClassId = 1, ClassName = "1班" });

            //克隆出来的孙悟空
            MonkeyKingPrototype cloneMonkeyKing = monkeyKingPrototype.Clone() as ConcretePrototype;

            monkeyKingPrototype.Class.ClassId = 2;
            monkeyKingPrototype.Class.ClassName = "2班";

            Console.WriteLine($"孙悟空原型所在的班级:id:{monkeyKingPrototype.Class.ClassId}, name:{monkeyKingPrototype.Class.ClassName}");
            Console.WriteLine($"克隆孙悟空所在的班级:id:{cloneMonkeyKing.Class.ClassId}, name:{cloneMonkeyKing.Class.ClassName}");


            Console.Read();
        }
    }

    /// <summary>
    /// 孙悟空原型类
    /// </summary>
    [Serializable]
    public abstract class MonkeyKingPrototype
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public Class Class { get; set; }

        protected MonkeyKingPrototype(int id, string name, Class c)
        {
            this.Id = id;
            this.Name = name;
            this.Class = c;
        }

        /// <summary>
        /// 克隆方法
        /// </summary>
        public abstract MonkeyKingPrototype Clone();
    }

    /// <summary>
    /// 创建具体原型
    /// </summary>
    [Serializable]
    public class ConcretePrototype : MonkeyKingPrototype
    {
        public ConcretePrototype(int id, string name, Class c) : base(id, name, c)
        {

        }

        ///// <summary>
        ///// 克隆方法,浅拷贝
        ///// </summary>
        //public override MonkeyKingPrototype Clone()
        //{
        //    return (MonkeyKingPrototype)this.MemberwiseClone();
        //}

        /// <summary>
        /// 使用序列化进行深拷贝
        /// </summary>
        /// <returns></returns>
        public override MonkeyKingPrototype Clone()
        {
            return SerializeHelper.FormatterByteObject(SerializeHelper.FormatterObjectBytes(this)) as MonkeyKingPrototype;
        }
    }

    /// <summary>
    /// 所在班级
    /// </summary>
    [Serializable]
    public class Class
    {
        /// <summary>
        /// 班级ID
        /// </summary>
        public int ClassId { get; set; }

        /// <summary>
        /// 班级名称
        /// </summary>
        public string ClassName { get; set; }
    }

    /// <summary>
    /// 序列化帮助类
    /// </summary>
    public class SerializeHelper
    {

        /// <summary>
        /// 序列化
        /// </summary>
        public static byte[] FormatterObjectBytes(object obj)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(ms, obj);
                return ms.GetBuffer();
            }
        }

        /// <summary>
        /// 反序列化
        /// </summary>
        /// <param name="buffer"></param>
        /// <returns></returns>
        public static object FormatterByteObject(byte[] buffer)
        {
            using (MemoryStream ms = new MemoryStream(buffer))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                return formatter.Deserialize(ms);
            }
        }
    }

        运行结果:

G[G_AK$P1LY}S}HI}MPTF9R.png

六、原型模式的实现要点

        原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些“易变类”拥有“稳定的接口”。

        原型模式对于“如何创建易变类的实体对象”采用“原型克隆”的方法来做,它使得我们可以非常灵活地创建“拥有某些稳定接口”的新对象--所需工作仅仅是注册一个新类的对象(即原型),然后在任何需要的地方不断地克隆。

        原型模式中的Clone方法可以利用.NET中的Object类的MemberwiseClone()方法或者序列化来实现深浅拷贝。

        原型模式的优点:

             1、原型模式向客户端隐藏了创建实例的复杂性。

            2、原型模式允许动态增加或减少产品类。

            3、原型模式简化了实例的创建结构。

            4、产品类不需要事先确定产品的等级结构,因为原型模式适用于任何的等级结构。

        原型模式的缺点:

            1、每个类必须有一个克隆方法。

            2、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。

        

分享到: 网站分享代码

发表评论

快捷回复:

评论列表 (暂无评论,1033人围观)参与讨论

还没有评论,来说两句吧...