首页 > 代码库 > 原型模式(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)