原型模式(Prototype Pattern)
一、什么是原型模式
原型模式(Prototype Pattern)也是一种创建型模式,它关注的是大量相似对象的创建问题。我们经常会遇到这样的情况:在系统中要创建大量的对象,这些对象之间具有几乎完全相同的功能,只是在细节上有一点儿差别。
意图
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
结构图
原型模式的基本实现
原型类:
1 public abstract class Prototype 2 { 3 private int id; 4 5 public Prototype(int id) 6 { 7 this.id = id; 8 } 9 10 public int Id 11 { 12 get { return id; } 13 } 14 15 public abstract Prototype Clone(); 16 }
具体的原型类:
1 public class ConcretePrototype : Prototype 2 { 3 public ConcretePrototype(int id) : base(id) { } 4 5 public override Prototype Clone() 6 { 7 return (Prototype)this.MemberwiseClone(); 8 } 9 }
客户端代码:
1 public class Client 2 { 3 public static void Main(string[] args) 4 { 5 ConcretePrototype cp1 = new ConcretePrototype(1); 6 ConcretePrototype cp2 = cp1.Clone() as ConcretePrototype; 7 8 Console.WriteLine(cp1.Id); 9 Console.WriteLine(cp2.Id); 10 11 Console.Read(); 12 } 13 }
对于.NET而言,那个原型抽象类Prototype是用不着的,因为克隆实在是太常用了,所以.NET在System命名空间中提供了ICloneable接口,其中就是唯一的一个方法Clone(),这样你就只需要实现这个接口就可以完成原型模式了。
具体原型类就变成了以下代码:
1 public class ConcretePrototype : ICloneable 2 { 3 private int id; 4 5 public int Id 6 { 7 get { return id; } 8 } 9 10 public ConcretePrototype(int id) 11 { 12 this.id = id; 13 } 14 15 public object Clone() 16 { 17 return (ConcretePrototype)this.MemberwiseClone(); 18 } 19 }
二、原型模式的实例
需求:小沈今年刚大学毕业,正在找工作。星期天上午要去招聘会,现在正在准备招聘会上用的简历,简历内容包括姓名,年龄,性别的基本信息,还包括工作经验,联系方式等信息。往往招聘会去都要带上好几十份简历,我们总不能好几十份简历都手写吧?那怎么办呢?对了,只要写一份,然后复印上20来份就行了。这就是典型的原型模式。接下来,我们看看使用程序如何来实现。
1、定义一个简历类(Resume),让其实现ICloneable接口,以实现原型模式
1 public class Resume : ICloneable 2 { 3 public string Name { get; set; } 4 5 public int Age { get; set; } 6 7 public string Gender { get; set; } 8 9 public string ContactPhone { get; set; } 10 11 public string WorkTime { get; set; } 12 13 public string Company { get; set; } 14 15 public string Position { get; set; } 16 17 public void Display() 18 { 19 Console.WriteLine("姓名:" + Name); 20 Console.WriteLine("年龄:" + Age); 21 Console.WriteLine("性别:" + Gender); 22 Console.WriteLine("联系电话:" + ContactPhone); 23 Console.WriteLine("工作经验:" + WorkTime + "\t" + Company + "\t" + Position); 24 } 25 26 public object Clone() 27 { 28 return (Resume)this.MemberwiseClone(); 29 } 30 }
2、然后我们写客户端代码
1 public class Client 2 { 3 public static void Main(string[] args) 4 { 5 Resume resume1 = new Resume 6 { 7 Name = "小沈", 8 Age = 22, 9 Gender = "男", 10 ContactPhone = "13423233232", 11 WorkTime = "2011.7~2012.1", 12 Company = "XXX 公司", 13 Position = "程序员" 14 }; 15 16 Resume resume2 = (Resume)resume1.Clone(); 17 18 resume1.Display(); 19 resume2.Display(); 20 21 Console.Read(); 22 } 23 }
好,我们看看运行情况
在实际生活中,我们简历中的联系方式一般不单单是就一个联系电话,可能还包括你的住址、邮政编码等信息。因此我们可以把联系方式抽象出一个类来。
1 public class Contact 2 { 3 public string Address { get; set; } 4 5 public string Phone { get; set; } 6 7 public string PostCode { get; set; } 8 }
我们也可以把工作经验抽象出一个类来。
1 public class WorkExperience 2 { 3 public string WorkTime { get; set; } 4 5 public string Company { get; set; } 6 7 public string Position { get; set; } 8 }
此时我们的简历类就变成了
1 public class Resume : ICloneable 2 { 3 public string Name { get; set; } 4 5 public int Age { get; set; } 6 7 public string Gender { get; set; } 8 9 public Contact Contact { get; set; } 10 11 public WorkExperience WorkExperience { get; set; } 12 13 public void Display() 14 { 15 Console.WriteLine("姓名:" + Name); 16 Console.WriteLine("年龄:" + Age); 17 Console.WriteLine("性别:" + Gender); 18 Console.WriteLine("联系电话:" + Contact.Phone); 19 Console.WriteLine("联系地址:" + Contact.Address); 20 Console.WriteLine("邮政编码:" + Contact.PostCode); 21 Console.WriteLine("工作经验:" + WorkExperience.WorkTime + "\t" + WorkExperience.Company + "\t" + WorkExperience.Position); 22 } 23 24 public object Clone() 25 { 26 return (Resume)this.MemberwiseClone(); 27 } 28 }
客户端代码修改为
1 public class Client 2 { 3 public static void Main(string[] args) 4 { 5 Resume resume1 = new Resume 6 { 7 Name = "小沈", 8 Age = 22, 9 Gender = "男", 10 Contact = new Contact{ Address = "仙霞路12号309室", Phone = "52066999", PostCode = "200120" }, 11 WorkExperience = new WorkExperience { WorkTime = "2011.7~2012.1", Company = "XXX公司", Position = "程序员" } 12 }; 13 14 Resume resume2 = (Resume)resume1.Clone(); 15 resume2.Contact.Phone = "13312313232"; 16 17 resume1.Display(); 18 Console.WriteLine("===================================================="); 19 resume2.Display(); 20 21 Console.Read(); 22 } 23 }
最后的运行结果为
我们看了运行结果之后发现了问题,我们客户端代码中手动修改了resume2对象的联系电话的值,resume2.Contact.Phone = "13312313232";,没有修改过resume1对象的联系电话的值,为什么我修改了resume2对象的联系方式的值,resume1对象的联系方式的值也被修改成和resume2对象的联系电话的值一样的值了呢?这就是因为我们Resume类中的Clone()方法采用的是浅拷贝内存表。
三、浅拷贝和深拷贝
浅拷贝:被拷贝对象的所有变量都含有与原来的对象相同的值。而所有的其他对象的引用都仍然指向原来的对象。
深拷贝:把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。
弄明白了什么是浅拷贝,什么是深拷贝后,我们就可以改善我们实例中简历类,实现对象的深拷贝了,我们看以下代码,就是在.NET中实现对象的深拷贝。
修改后的简历程序的代码:
1 [Serializable] 2 public class Resume : ICloneable 3 { 4 public string Name { get; set; } 5 6 public int Age { get; set; } 7 8 public string Gender { get; set; } 9 10 public Contact Contact { get; set; } 11 12 public WorkExperience WorkExperience { get; set; } 13 14 public void Display() 15 { 16 Console.WriteLine("姓名:" + Name); 17 Console.WriteLine("年龄:" + Age); 18 Console.WriteLine("性别:" + Gender); 19 Console.WriteLine("联系电话:" + Contact.Phone); 20 Console.WriteLine("联系地址:" + Contact.Address); 21 Console.WriteLine("邮政编码:" + Contact.PostCode); 22 Console.WriteLine("工作经验:" + WorkExperience.WorkTime + "\t" + WorkExperience.Company + "\t" + WorkExperience.Position); 23 } 24 25 public object Clone() 26 { 27 MemoryStream memoryStream = new MemoryStream(); 28 BinaryFormatter formatter = new BinaryFormatter(); 29 formatter.Serialize(memoryStream, this); 30 memoryStream.Position = 0; 31 return (Resume)formatter.Deserialize(memoryStream); 32 } 33 } 34 35 [Serializable] 36 public class Contact 37 { 38 public string Address { get; set; } 39 40 public string Phone { get; set; } 41 42 public string PostCode { get; set; } 43 } 44 45 [Serializable] 46 public class WorkExperience 47 { 48 public string WorkTime { get; set; } 49 50 public string Company { get; set; } 51 52 public string Position { get; set; } 53 } 54 55 public class Program3 56 { 57 public static void Main(string[] args) 58 { 59 Resume resume1 = new Resume 60 { 61 Name = "小沈", 62 Age = 22, 63 Gender = "男", 64 Contact = new Contact{ Address = "仙霞路12号309室", Phone = "52066999", PostCode = "200120" }, 65 WorkExperience = new WorkExperience { WorkTime = "2011.7~2012.1", Company = "XXX公司", Position = "程序员" } 66 }; 67 68 Resume resume2 = (Resume)resume1.Clone(); 69 resume2.Contact.Phone = "13312313232"; 70 71 resume1.Display(); 72 Console.WriteLine("===================================================="); 73 resume2.Display(); 74 75 Console.Read(); 76 } 77 }
四、原型模式另一种实现(登记式)
需求:我们考虑这样一个场景,假定我们要开发一个调色板,用户单击调色板上任一个方块,将会返回一个对应的颜色的实例,下面我们看看如何通过原型模式来达到系统动态加载具体产品的目的。
1 [Serializable] 2 public class ConcreteColor : ICloneable 3 { 4 private int red, green, blue; 5 6 public ConcreteColor(int red, int green, int blue) 7 { 8 this.red = red; 9 this.blue = blue; 10 this.green = green; 11 } 12 13 public object Clone() 14 { 15 MemoryStream memoryStream = new MemoryStream(); 16 BinaryFormatter formatter = new BinaryFormatter(); 17 formatter.Serialize(memoryStream, this); 18 memoryStream.Position = 0; 19 return (ConcreteColor)formatter.Deserialize(memoryStream); 20 } 21 22 public void Display(string colorName) 23 { 24 Console.WriteLine("{0}'s RGB Values are: {1},{2},{3}",colorName, red, green, blue); 25 } 26 } 27 28 public class ColorManager 29 { 30 public Hashtable colors = new Hashtable(); 31 32 public ConcreteColor this[string name] 33 { 34 get 35 { 36 return (ConcreteColor)colors[name]; 37 } 38 39 set 40 { 41 colors.Add(name, value); 42 } 43 } 44 } 45 46 public class Client 47 { 48 public static void Main(string[] args) 49 { 50 ColorManager colorManager = new ColorManager(); 51 colorManager["red"] = new ConcreteColor(255, 0, 0); 52 colorManager["green"] = new ConcreteColor(0, 255, 0); 53 colorManager["blue"] = new ConcreteColor(0, 0, 255); 54 colorManager["angry"] = new ConcreteColor(255, 54, 0); 55 colorManager["peace"] = new ConcreteColor(128, 211, 128); 56 colorManager["flame"] = new ConcreteColor(211, 34, 20); 57 58 string colorName = "red"; 59 ConcreteColor color = (ConcreteColor)colorManager[colorName].Clone(); 60 color.Display(colorName); 61 62 string colorName2 = "green"; 63 ConcreteColor color1 = (ConcreteColor)colorManager[colorName2].Clone(); 64 color1.Display(colorName2); 65 66 Console.Read(); 67 } 68 }
五、原型模式的总结
实现要点
- 使用原型管理器,体现在一个系统中原型数目不固定时,可以动态的创建和销毁,如上面的举的调色板的例子。
- 实现克隆操作,在.NET中可以使用Object类的MemberwiseClone()方法来实现对象的浅表拷贝或通过序列化的方式来实现深拷贝。
- Prototype模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些“易变类”拥有稳定的接口。
效果
- 它对客户隐藏了具体的产品类,因此减少了客户知道的名字的数目。
- Prototype模式允许客户只通过注册原型实例就可以将一个具体产品类并入到系统中,客户可以在运行时刻建立和删除原型。
- 减少了子类构造,Prototype模式是克隆一个原型而不是请求工厂方法创建一个,所以它不需要一个与具体产品类平行的Creater类层次。
- Portotype模式具有给一个应用软件动态加载新功能的能力。由于Prototype的独立性较高,可以很容易动态加载新功能而不影响老系统。
- 产品类不需要非得有任何事先确定的等级结构,因为Prototype模式适用于任何的等级结构
- Prototype模式的最主要缺点就是每一个类必须配备一个克隆方法。而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事。
适用性
- 当一个系统应该独立于它的产品创建,构成和表示时;
- 当要实例化的类是在运行时刻指定时,例如,通过动态装载;
- 为了避免创建一个与产品类层次平行的工厂类层次时;
- 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。