• 原型模式(8)


    今天我们来讲一下原型模式。老样子,我们先举一个案例:

    一、案例

    我们找工作,需要投简历,一份简历是不够的,我们需要多复制几分简历。

    好,我们用简单的控制台程序来完成上述的需求。

    二、演绎

    1、第一步演绎

     1     /// <summary>
     2     /// 简历类
     3     /// </summary>
     4     class Resume
     5     {
     6         private string name;//姓名
     7         private string sex;//性别
     8         private string age;//年龄
     9         private string timeArea;//工作时间
    10         private string company;//工作单位
    11         
    12         public Resume(string name)
    13         {
    14             this.name = name;
    15         }
    16         //设置个人信息
    17         public void SetPersonalInfo(string sex, string age)
    18         {
    19             this.sex = sex; 
    20             this.age = age;
    21         }
    22         //设置工作经历
    23         public void SetWorkExperience(string timeArea, string company)
    24         {
    25             this.timeArea = timeArea;
    26             this.company = company;
    27         }
    28         //显示
    29         public void Display()
    30         {
    31             Console.WriteLine($"{name}{sex}{age}");
    32             Console.WriteLine($"工作经历:{timeArea}{company}");
    33         }
    34     }

    客户端调用:

     1         public static void Main()
     2         {
     3             Resume a = new Resume("小魔王");
     4             a.SetPersonalInfo("", "29");
     5             a.SetWorkExperience("2000-2000", "XX公司");
     6 
     7             Resume b = new Resume("小魔王");
     8             b.SetPersonalInfo("", "29");
     9             b.SetWorkExperience("2000-2000", "XX公司");
    10 
    11             Resume c = new Resume("小魔王");
    12             c.SetPersonalInfo("", "29");
    13             c.SetWorkExperience("2000-2000", "XX公司");
    14 
    15             a.Display();
    16             b.Display();
    17             c.Display();
    18             Console.Read();
    19         }

    OK,我们实现了上述的功能,搞了三份简历出来。我们可以通过复制粘贴,复制更多分简历出来。

    那么问题来了。

    ①需要三份简历,客户端就需要实例化三次,如果需要20份简历,100份简历,那么客户端岂不是要疯掉了。

    ②如果我简历上写错了一个字,年龄29要改成30,那么客户端岂不是需要修改3次,20份简历的话,就需要修改20次,好恐怖啊。

    2、第二步演绎

    针对上述问题,我们将代码做一下修改:

    我们将客户端进行一下优化:

     1             Resume a = new Resume("小魔王");
     2             a.SetPersonalInfo("", "29");
     3             a.SetWorkExperience("2000-2000", "XX公司");
     4             Resume b = a;
     5             Resume c = a;
     6             a.Display();
     7             b.Display();
     8             c.Display();
     9             Console.Read();
    10         }

    哈哈,这样的话,省却了不少的代码呢。

    好,我们来看一看他的本质, Resume b = a;Resume c = a; 实际上是传的引用,而不是传值,就如同,有b、c两张纸写着简历在a处一样,实际没有任何内容。

    好,那么接下来,我们正式的给大家介绍一下原型模式,他很好的解决了上述的问题。

     什么是原型模式呢?

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

    说白了,原型模式就是从一个对象创造另外一个可定制的对象,而且不需要知道任何的创造细节。

    好,下面我们来看一下最基本的原型模式的代码

     1     /// <summary>
     2     /// 原型类
     3     /// </summary>
     4     internal abstract class Prototype
     5     {
     6         public string Id { get; }
     7 
     8         protected Prototype(string id)
     9         {
    10             this.Id = id;
    11         }
    12         
    13         public abstract Prototype Clone();
    14     }
    15     /// <summary>
    16     /// 具体的原型方法
    17     /// </summary>
    18     class ConcretePrototype1 : Prototype
    19     {
    20         public ConcretePrototype1(string id) : base(id)
    21         {
    22 
    23         }
    24         //克隆
    25         public override Prototype Clone()
    26         {
    27             return (Prototype) this.MemberwiseClone();//MemberwiseClone()这个方法大家可以去百度谷歌一下。
    28         }
    29     }

    客户端调用

    1         public static void Main()
    2         {
    3             ConcretePrototype1 p1 = new ConcretePrototype1("I");
    4             ConcretePrototype1 c1 = (ConcretePrototype1) p1.Clone();
    5             Console.WriteLine($"Cloned:{c1.Id}");
    6             Console.ReadKey();
    7         }

    好了,上面就是原型模式的基本代码,核心就是一个Clone() 方法。

    因为就克隆而言,在我们的日常编码中,太常用了,所以,.net在System命名空间中提供了一个ICloneable的接口,这个接口中有一个唯一的方法Clone(),跟我们的抽象类Prototype很相似,所以,以后我们想要实现原型模式,就不用费力去写Prototype这个抽象类了,只需要将具体的原型类继承ICloneable这个接口即可,就能实现原型模式了。

    好,下面我们就将我们文章开头中提到的案例用原型模式来写一遍。

    3、第三步演绎

    案例中我么Resume类只需要继承ICloneable接口,然后实现这个接口中的Clone()方法就可以了。其他的不用变。

     1 class Resume:ICloneable
     2     {
     3         private string name;//姓名
     4         private string sex;//性别
     5         private string age;//年龄
     6         private string timeArea;//工作时间
     7         private string company;//工作单位
     8         
     9         public Resume(string name)
    10         {
    11             this.name = name;
    12         }
    13         //设置个人信息
    14         public void SetPersonalInfo(string sex, string age)
    15         {
    16             this.sex = sex; 
    17             this.age = age;
    18         }
    19         //设置工作经历
    20         public void SetWorkExperience(string timeArea, string company)
    21         {
    22             this.timeArea = timeArea;
    23             this.company = company;
    24         }
    25         //显示
    26         public void Display()
    27         {
    28             Console.WriteLine($"{name}{sex}{age}");
    29             Console.WriteLine($"工作经历:{timeArea}{company}");
    30         }
    31         //实现ICloneable接口中的Clone()方法
    32         public object Clone()
    33         {
    34             return this.MemberwiseClone();
    35         }
    36     }

    客户端

     1         public static void Main()
     2         {
     3             //第一份简历
     4             Resume a = new Resume("小魔王");
     5             a.SetPersonalInfo("", "29");
     6             a.SetWorkExperience("2000-2000","XX公司");
     7 
     8             //生成一份新简历
     9             Resume b = (Resume) a.Clone();
    10             //可以对新简历的细节进行修改。
    11             b.SetWorkExperience("1998-1999","AA公司");
    12 
    13             Resume c = (Resume) a.Clone();            
    14             c.SetPersonalInfo("", "28");
    15 
    16             a.Display();
    17             b.Display();
    18             c.Display();
    19             Console.ReadKey();
    20         }

    好了,下面我们来看一下这个原型模式有什么好处。

    相比之前的代码,每new一次,就会调用一下构造函数,如果创造这个对象的过程很复杂,也就是说,构造函数很复杂的话,那么调用构造函数执行起来就非常的慢,浪费很多的资源。这么多次的执行这个初始化操作,效率实在是太低了。所以,一般在初始化i信息不变的情况下,克隆是最好的变法,既隐藏了对象创建的细节,又提高的效率。

    原型模式就这么结束了吗?那我在给小伙伴们挖个坑来跳跳,哈哈。

    现在 Resume中的字段都是string类型的,也就是说都是值类型的。如果,我们将Resume中的字段换成引用类型的,将会出现怎样的效果呢?

    来,我们来变一下。

    我们增加一个工作经历的类

    1     class WorkExperience
    2     {
    3         public string workDate { get; set; }
    4         public string company { get; set; }
    5     }

    然后,Resume类中就这么写了

     1     class Resume : ICloneable
     2     {
     3         private string name;//姓名
     4         private string sex;//性别
     5         private string age;//年龄
     6         private WorkExperience work;
     7 
     8         public Resume(string name)
     9         {
    10             this.name = name;
    11             work = new WorkExperience();
    12         }
    13         //设置个人信息
    14         public void SetPersonalInfo(string sex, string age)
    15         {
    16             this.sex = sex;
    17             this.age = age;
    18         }
    19         //设置工作经历
    20         public void SetWorkExperience(string timeArea, string company)
    21         {
    22             work.workDate = timeArea;
    23             work.company = company;
    24         }
    25         //显示
    26         public void Display()
    27         {
    28             Console.WriteLine($"{name}{sex}{age}");
    29             Console.WriteLine($"工作经历:{  work.workDate}{ work.company}");
    30         }
    31         //实现ICloneable接口中的Clone()方法
    32         public object Clone()
    33         {
    34             return this.MemberwiseClone();
    35         }
    36     }

    客户端:

     1         public static void Main()
     2         {
     3             //第一份简历
     4             Resume a = new Resume("小魔王");
     5             a.SetPersonalInfo("", "29");
     6             a.SetWorkExperience("2000-2000", "XX公司");
     7 
     8             //生成一份新简历
     9             Resume b = (Resume)a.Clone();
    10             //可以对新简历的细节进行修改。
    11             b.SetWorkExperience("1998-1999", "AA公司");
    12 
    13             Resume c = (Resume)a.Clone();
    14             c.SetPersonalInfo("", "28");
    15 
    16             a.Display();
    17             b.Display();
    18             c.Display();
    19             Console.ReadKey();
    20         }

    好,我们来运行一下。

    嗯?有什么不对吗?这不是我预期的效果啊~

    工作经历结果就是我们最后设置的那个值,这是什么原因呢? 我们可以查一下MemberwiseClone()就应该知道是什么原因了。

    好,那么我们该如何解决这个问题呢?

    1、将WorkExperience 类 实现 ICloneable 接口

    1     class WorkExperience:ICloneable
    2     {
    3         public string workDate { get; set; }
    4         public string company { get; set; }
    5         public object Clone()
    6         {
    7             return MemberwiseClone();
    8         }
    9     }

    2、修改Resume类

     1     class Resume : ICloneable
     2     {
     3         private string name;//姓名
     4         private string sex;//性别
     5         private string age;//年龄
     6         private WorkExperience work;
     7 
     8         public Resume(string name)
     9         {
    10             this.name = name;
    11             work = new WorkExperience();
    12         }
    13         //私有构造函数,以便克隆工作经历的数据
    14         private Resume(WorkExperience work)
    15         {
    16             this.work = (WorkExperience)work.Clone();
    17         }
    18         //设置个人信息
    19         public void SetPersonalInfo(string sex, string age)
    20         {
    21             this.sex = sex;
    22             this.age = age;
    23         }
    24         //设置工作经历
    25         public void SetWorkExperience(string timeArea, string company)
    26         {
    27             work.workDate = timeArea;
    28             work.company = company;
    29         }
    30         //显示
    31         public void Display()
    32         {
    33             Console.WriteLine($"{name}{sex}{age}");
    34             Console.WriteLine($"工作经历:{  work.workDate}{ work.company}");
    35         }
    36         //实现ICloneable接口中的Clone()方法
    37         public object Clone()
    38         {
    39             Resume obj = new Resume(this.work);//调用/私有构造函数,将工作经历克隆。
    40             obj.name = this.name;
    41             obj.sex = this.sex;
    42             obj.age = this.age;
    43             return obj;
    44         }
    45     }

    好,下面运行一下,果然达到了我们预期的效果。

    这两种克隆的方式分别叫做 浅复制,深复制。

    其实,在一些特定的场合,我们会经常用到,例如: DataSet 中,有Clone 和 Copy 两个方法,一个是浅复制,一个是深复制。

    好了,说了这么多了,小伙伴们好好体会一下吧!

    几天就讲到这了,下一篇会讲 模板方法模式


    本系列将持续更新,喜欢的小伙伴可以点一下关注和推荐,谢谢大家的支持。

  • 相关阅读:
    Design + Code (iOS)
    Objective-C Programming (2nd Edition)
    iOS Programming The Big Nerd Ranch Guide (4th Edition)
    反射
    面向对象
    人狗大战
    数据结构初识(三级菜单)
    面向对象(组合)
    练习
    re模块
  • 原文地址:https://www.cnblogs.com/xiaomowang/p/6290486.html
Copyright © 2020-2023  润新知