zoukankan      html  css  js  c++  java
  • 设计模式之-原型模式

    原型模式是一种创建型设计模式,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

  • 相关阅读:
    java操作生成jar包 和写入jar包
    jboss配置jndi连接池
    windows 域的LDAP查询相关举例
    LDAP error Code 及解决方法
    HDU 6417
    CF1299D Around the World
    codechef Chef and The Colored Grid
    Educational Codeforces Round 82 (Rated for Div. 2)
    CF1237F Balanced Domino Placements
    CF1254E Send Tree to Charlie
  • 原文地址:https://www.cnblogs.com/hhzblogs/p/10374743.html
Copyright © 2011-2022 走看看