• 设计模式之-原型模式


    原型模式是一种创建型设计模式,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节。工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。

    接下来我们就以简历为例,当我们要面试时,通常会通过打印机将简历一份份打印出来。

    简历代码初步实现

    简历类:

    namespace PrototypePattern
    {
        /// <summary>
        /// 简历
        /// </summary>
        public class Resume
        {
            public string Name { get; set; }
            public string Sex { get; set; }
            public int Age { get; set; }
            public string TimeArea { get; set; }
            public string Company { get; set; }
            public Resume(string name)
            {
                this.Name = name;
            }
            //设置个人信息
            public void SetPersonalInfo(string sex, int age)
            {
                this.Sex = sex;
                this.Age = age;
            }
            //设置工作经历
            public void SetWorkExperience(string timeArea, string company)
            {
                this.TimeArea = timeArea;
                this.Company = company;
            }
            //显示
            public void Show()
            {
                Console.WriteLine($"姓名:{this.Name} 性别:{this.Sex} 年龄:{this.Age}");
                Console.WriteLine($"工作经历:{this.TimeArea} {this.Company}");
            }
        }
    }

    客户端调用代码:

    static void Main(string[] args)
    {
        try
        {
            Resume a = new Resume("tom");
            a.SetPersonalInfo("", 21);
            a.SetWorkExperience("1998-2000", "XXX公司");
    
            Resume b = new Resume("tom");
            b.SetPersonalInfo("", 21);
            b.SetWorkExperience("1998-2000", "XXX公司");
    
            Resume c = new Resume("tom");
            c.SetPersonalInfo("", 21);
            c.SetWorkExperience("1998-2000", "XXX公司");
    
            a.Show();
            b.Show();
            c.Show();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        Console.ReadKey();
    }

    结果如下:

    没毛病,出现了3张一模一模的简历。在这里我们需要3张简历,所以我们实例化了3次。但我觉得这样的客户端很麻烦,如果需要20份,200份呢,我们难道需要实例化20次、200次?

    如果简历中写错了一个字,把1998错写成了1999,这时我们就需要修改20,200次。。。

     Oh My God,这实在是太糟糕了。

    这时有人会说,我们可以这样写:

    Resume a = new Resume("tom");
    a.SetPersonalInfo("", 21);
    a.SetWorkExperience("1998-2000", "XXX公司");
    
    Resume b = a;
    Resume c = a;
    
    a.Show();
    b.Show();
    c.Show();

    这样确实可以实现和上面一样的效果,但这只是“传递引用”,并不是传值,这样就相当于在b纸张和c纸张上写着简历在a纸张上一样。

    现在我想让这个简历其它的全部一样,就工作经历不同,怎么办?上面的操作是“传递引用”,当我们修改一个简历的工作经历时候,其它的简历也会跟随着改变。

    原型模式

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

    原型模式其实就是从一个对象再创建一个可定制的对象,而且不需要知道任何的细节。

    原型类:

    namespace PrototypePattern.PrototypePattern
    {
        abstract class Prototype
        {
            public int Id { get; set; }
            public Prototype(int id)
            {
                this.Id = id;
            }
    
            public abstract Prototype Clone();//这个抽象类的关键就是这个Clone方法
        }
    }

    具体原型类:

    namespace PrototypePattern.PrototypePattern
    {
        /// <summary>
        /// 具体原型
        /// </summary>
        class ConcretePrototype1 : Prototype
        {
            public ConcretePrototype1(int id) : base(id)
            {
    
            }
            /// <summary>
            /// 创建当前对象的浅表副本。
            /// 方法是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。
            /// 如果字段是值类型的,则对该字段进行逐位复制。
            /// 如果字段是引用类型的,则复制引用,但不复制引用的对象,因此,原始对象及其副本引用同一个对象
            /// </summary>
            /// <returns></returns>
            public override Prototype Clone()
            {
                return (Prototype)this.MemberwiseClone();
            }
        }
    }

    客户端代码:

    ConcretePrototype1 p1 = new ConcretePrototype1(1);
    ConcretePrototype1 c1 = (ConcretePrototype1)p1.Clone();
    
    Console.WriteLine($"p1.Id={p1.Id},c1.Id={c1.Id}");
    c1.Id = 2;
    Console.WriteLine($"p1.Id={p1.Id},c1.Id={c1.Id}");

    结果如下:

    对于.net而言,原型抽象类Prototype类是用不类的,因为克隆实在是太常用了,所以.net在System命名空间中提供了ICloneable接口,其中就只有唯一的一个方法Clone(),这样我们就可以只需要实现这个接口就可以完成原型模式了。

    简历的原型实现

     简历类:

    namespace PrototypePattern
    {
        /// <summary>
        /// 简历
        /// </summary>
        public class Resume : ICloneable
        {
            public string Name { get; set; }
            public string Sex { get; set; }
            public int Age { get; set; }
            public string TimeArea { get; set; }
            public string Company { get; set; }
            public Resume(string name)
            {
                this.Name = name;
            }
            //设置个人信息
            public void SetPersonalInfo(string sex, int age)
            {
                this.Sex = sex;
                this.Age = age;
            }
            //设置工作经历
            public void SetWorkExperience(string timeArea, string company)
            {
                this.TimeArea = timeArea;
                this.Company = company;
            }
            //显示
            public void Show()
            {
                Console.WriteLine($"姓名:{this.Name} 性别:{this.Sex} 年龄:{this.Age}");
                Console.WriteLine($"工作经历:{this.TimeArea} {this.Company}");
            }
    
            public object Clone()
            {
                return (Resume)this.MemberwiseClone();
            }
        }
    }

    客户端调用代码:

    Resume a = new Resume("tom");
    a.SetPersonalInfo("", 21);
    a.SetWorkExperience("1998-2000", "XXX公司");
    
    Resume b = (Resume)a.Clone();
    b.SetWorkExperience("2000-2002", "YYY公司");
    
    Resume c = (Resume)a.Clone();
    c.SetWorkExperience("2002-2004", "ZZZ公司");
    
    a.Show();
    b.Show();
    c.Show();

    结果如下:

    这样实现好多了,而且当你想要改其中某一份简历的时候,直接修改就好了,并不会影响到其它的简历。

    一般在初始化信息不发生变化的情况下,克隆是最好的方法。这既隐藏了对象创建的细节,又提高了性能,何乐而不为呢?

    浅拷贝与深拷贝

    当你自以为完美的时候,然后结果却往往令你大失所望。

    哈哈,没错,现在简历对象的所有属性都是值类型或者重写过运算符的引用类型。那么当你的简历类中含有“引用类型”时,那么当你克隆简历时,这个引用类型会不会也被克隆过来呢?

    我们简历类中有一个“设置工作经历”的方法,在实际开发中,都会有一个工作经历类,现在我们添加一个“工作经历”类, 当中有“时间区间”和“公司名称”属性,类直接调用这个对象即可。

    工作经历类:

    namespace PrototypePattern
    {
        public class WorkExperience
        {
            public string WorkDate { get; set; }
            public string Company { get; set; }
        }
    }

    简历类:

    namespace PrototypePattern
    {
        /// <summary>
        /// 简历
        /// </summary>
        public class Resume : ICloneable
        {
            public string Name { get; set; }
            public string Sex { get; set; }
            public int Age { get; set; }
            public WorkExperience WorkExperience { get; set; }
            public Resume(string name)
            {
                this.Name = name;
                this.WorkExperience = new WorkExperience();
            }
            //设置个人信息
            public void SetPersonalInfo(string sex, int age)
            {
                this.Sex = sex;
                this.Age = age;
            }
            //设置工作经历
            public void SetWorkExperience(string timeArea, string company)
            {
                WorkExperience.WorkDate = timeArea;
                WorkExperience.Company = company;
            }
            //显示
            public void Show()
            {
                Console.WriteLine($"姓名:{this.Name} 性别:{this.Sex} 年龄:{this.Age}");
                Console.WriteLine($"工作经历:{this.WorkExperience.WorkDate} {this.WorkExperience.Company}");
            }
            public object Clone()
            {
                return (Resume)this.MemberwiseClone();
            }
        }
    }

    客户端代码调用:

        Resume a = new Resume("tom");
        a.SetPersonalInfo("", 21);
        a.SetWorkExperience("1998-2000", "XXX公司");
    
        Resume b = (Resume)a.Clone();
        b.SetWorkExperience("2000-2002", "YYY公司");
    
        Resume c = (Resume)a.Clone();
        c.SetWorkExperience("2002-2004", "ZZZ公司");
    
        a.Show();
        b.Show();
        c.Show();

    如果如下:

    由于MemberwiseClone()方法是浅表拷贝,对于值类型没啥问题,但对于引用类型,就只复制了引用,它们所指向的任是一个对象。那么怎么实现拷贝时,要将简历类当中的引用类型的对象拷贝一份,而不是拷贝引用呢?

    简历的深复制实现

    工作经历类:

    namespace PrototypePattern
    {
        public class WorkExperience:ICloneable
        {
            public string WorkDate { get; set; }
            public string Company { get; set; }
    
            public object Clone()
            {
                return (WorkExperience)this.MemberwiseClone();
            }
        }
    }

    简历类:

    namespace PrototypePattern
    {
        /// <summary>
        /// 简历
        /// </summary>
        public class Resume : ICloneable
        {
            public string Name { get; set; }
            public string Sex { get; set; }
            public int Age { get; set; }
            public WorkExperience WorkExperience { get; set; }
            public Resume(string name)
            {
                this.Name = name;
                this.WorkExperience = new WorkExperience();
            }
            public Resume(WorkExperience work)
            {
                this.WorkExperience = (WorkExperience)work.Clone();
            }
            //设置个人信息
            public void SetPersonalInfo(string sex, int age)
            {
                this.Sex = sex;
                this.Age = age;
            }
            //设置工作经历
            public void SetWorkExperience(string timeArea, string company)
            {
                WorkExperience.WorkDate = timeArea;
                WorkExperience.Company = company;
            }
            //显示
            public void Show()
            {
                Console.WriteLine($"姓名:{this.Name} 性别:{this.Sex} 年龄:{this.Age}");
                Console.WriteLine($"工作经历:{this.WorkExperience.WorkDate} {this.WorkExperience.Company}");
            }
            public object Clone()
            {
                Resume obj = new Resume(this.WorkExperience);
                obj.Name = this.Name;
                obj.Sex = this.Sex;
                obj.Age = this.Age;
                return obj;
            }
        }
    }

    客户端调用:

        Resume a = new Resume("tom");
        a.SetPersonalInfo("", 21);
        a.SetWorkExperience("1998-2000", "XXX公司");
    
        Resume b = (Resume)a.Clone();
        b.SetWorkExperience("2000-2002", "YYY公司");
    
        Resume c = (Resume)a.Clone();
        c.SetWorkExperience("2002-2004", "ZZZ公司");
    
        a.Show();
        b.Show();
        c.Show();

     结果如下:

    注意:使用实现ICloneable接口来实现原型模式的深拷贝,如果类与类之前的关系比较简单的话还好,如果很复杂的话,通常会让你很头痛,这个可以推荐一个简单的方法。就是使用二进制序列化将要拷贝的对象(简历)先序列化,然后再反序列化成对象,这个简历当中的引用类型的属性就能实现深拷贝了。

    由于在一些特定的场合,会经常涉及到深拷贝或浅拷贝,比如说,数据集对象DataSet,它就有Clone()方法和Copy()方法,Clone()方法用来复制DataSet的结构,但不复制DataSet的数据 ,实现了原型模式的浅拷贝。Copy()方法不但复制结构 ,也复制数据,其实就是实现了原型模式的深拷贝。

    源代码地址:https://github.com/houzhenhuang/DesignPattern

  • 相关阅读:
    js面向对象编程-高级内容
    (转)js中的hasOwnProperty和isPrototypeOf方法
    Bootstrap_表单
    Bootstrap_表格
    Bootstrap_排版
    Bootstrap_网格系统
    Bootstrap_CSS概览
    redis的搜索组件 redis-search4j
    有哪些值得学习的spring boot开源项目?
    国内最火的10款Java开源项目,都是国人开发,CMS居多
  • 原文地址:https://www.cnblogs.com/hhzblogs/p/10374743.html
Copyright © 2020-2023  润新知