首页 > 代码库 > 原型模式(8)
原型模式(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 两个方法,一个是浅复制,一个是深复制。
好了,说了这么多了,小伙伴们好好体会一下吧!
几天就讲到这了,下一篇会讲 模板方法模式
本系列将持续更新,喜欢的小伙伴可以点一下关注和推荐,谢谢大家的支持。
原型模式(8)