设计模式(16)---原型模式
一、定义
原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
解释:有一个设计非常复杂的对象,如果需要得到多个这样对象的时候,可以先创建一个原型对象,然后使用原型对象clone出新的对象,从而实现减少内存消耗和类实例复用的目的。
二、UML类图及基本代码
基本代码:
abstract class Prototype { private string id; public string ID { get { return id; } } public Prototype(string id) { this.id = id; } public abstract Prototype Clone(); } class ConcretePrototype : Prototype { public ConcretePrototype(string id) : base(id) { } public override Prototype Clone() { return (Prototype)this.MemberwiseClone(); } }
客户端调用:
ConcretePrototype cp1 = new ConcretePrototype("a"); ConcretePrototype cp2 = (ConcretePrototype)cp1.Clone();
三、具体实例
编写一个简历,包含姓名、性别、年龄、工作经历等,然后复制多份进行显示。
实例代码及运行结果:
class Program { static void Main(string[] args) { Resume resume1 = new Resume("tom"); resume1.SetPersonInfo("man", "17"); resume1.SetWorkExperience("1980-1990", "xx company"); Resume resume2 = (Resume)resume1.Clone(); resume2.SetWorkExperience("1990-2000", "yy company"); Resume resume3 = (Resume)resume1.Clone(); resume3.SetPersonInfo("man", "19"); resume1.Display(); resume2.Display(); resume3.Display(); Console.Read(); } } class Resume : ICloneable { private string name; private string sex; private string age; private string timeArea; private string company; public Resume(string name) { this.name = name; } public void SetPersonInfo(string sex, string age) { this.sex = sex; this.age = age; } public void SetWorkExperience(string timeArea, string company) { this.timeArea = timeArea; this.company = company; } public void Display() { Console.WriteLine("{0} {1} {2}", name, sex, age); Console.WriteLine("workexperience:{0} {1}", timeArea, company); } public object Clone() { return (object)this.MemberwiseClone(); } }
实例延伸:
上述实例总MemberwiseClone()方法是:如果字段是值类型,则对该字段执行逐位复制,如果字段是引用类型,则复制引用,但不复制引用的对象。这称之为浅拷贝,被复制对象的所有变量都含有与原来对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。接下来,将工作经历单独拿出来作为一个类在简历类中引用。如下:
class Program { static void Main(string[] args) { Resume resume1 = new Resume("tom"); resume1.SetPersonInfo("man", "17"); resume1.SetWorkExperience("1980-1990", "xx company"); Resume resume2 = (Resume)resume1.Clone(); resume2.SetWorkExperience("1990-2000", "yy company"); Resume resume3 = (Resume)resume1.Clone(); resume1.SetWorkExperience("2000-2010", "zz company"); resume1.Display(); resume2.Display(); resume3.Display(); Console.Read(); } } class WorkExperience { private string timeArea; public string TimeArea { get { return timeArea; } set { timeArea = value; } } private string company; public string Company { get { return company; } set { company = value; } } } class Resume : ICloneable { private string name; private string sex; private string age; private WorkExperience work; public Resume(string name) { this.name = name; work = new WorkExperience(); } public void SetPersonInfo(string sex, string age) { this.sex = sex; this.age = age; } public void SetWorkExperience(string timeArea, string company) { work.TimeArea = timeArea; work.Company = company; } public void Display() { Console.WriteLine("{0} {1} {2}", name, sex, age); Console.WriteLine("workexperience:{0} {1}", work.TimeArea, work.Company); } public object Clone() { return (object)this.MemberwiseClone(); } }
结果如下:
分析:工作经历是有3段,但运行结果只显示一段。由此可见浅拷贝对于类中引用对象的复制并不成功。
四、概念讲解
浅拷贝:当对象的字段值被拷贝时,字段引用的对象不会被拷贝。例如,如果一个对象有一个指向字符串的字段,并且我们对该对象做了一个浅拷贝,那么这两个对象将引用同一个字符串。
深拷贝:对对象实例中字段引用的对象也进行拷贝,如果一个对象有一个指向字符串的字段,并且我们对该对象进行了深拷贝的话,那么我们将创建一个对象和一个新的字符串,新的对象将引用新的字符串。
也就是说,执行深拷贝创建的新对象和原来对象不会共享任何东西,改变一个对象对另外一个对象没有任何影响,而执行浅拷贝创建的新对象与原来对象共享成员,改变一个对象,另外一个对象的成员也会改变。
五、实例改进
同样是上述的实例,对于有引用字段的,本例使用深拷贝,代码如下:
class Program { static void Main(string[] args) { Resume resume1 = new Resume("tom"); resume1.SetPersonInfo("man", "17"); resume1.SetWorkExperience("1980-1990", "xx company"); Resume resume2 = (Resume)resume1.Clone(); resume2.SetWorkExperience("1990-2000", "yy company"); Resume resume3 = (Resume)resume1.Clone(); resume1.SetWorkExperience("2000-2010", "zz company"); resume1.Display(); resume2.Display(); resume3.Display(); Console.Read(); } } class WorkExperience:ICloneable //实现ICloneable接口 { private string timeArea; public string TimeArea { get { return timeArea; } set { timeArea = value; } } private string company; public string Company { get { return company; } set { company = value; } } public object Clone() { return (object)this.MemberwiseClone(); } } class Resume : ICloneable { private string name; private string sex; private string age; private WorkExperience work; public Resume(string name) { this.name = name; work = new WorkExperience(); } private Resume(WorkExperience work) { this.work = (WorkExperience)work.Clone();//提供Clone方法调用的私有构造函数,以便克隆工作经历的数据 } public void SetPersonInfo(string sex, string age) { this.sex = sex; this.age = age; } public void SetWorkExperience(string timeArea, string company) { work.TimeArea = timeArea; work.Company = company; } public void Display() { Console.WriteLine("{0} {1} {2}", name, sex, age); Console.WriteLine("workexperience:{0} {1}", work.TimeArea, work.Company); } public object Clone() { Resume obj = new Resume(this.work);//调用私有构造方法,让工作经历克隆完成,然后在给这个简历对象的相关字段赋值,最终返回一个深拷贝的对象 obj.name = this.name; obj.sex = this.sex; obj.age = this.age; return obj; } }
运行结果:
六、优缺点及适用场景
优点:
1)原型模式向客户隐藏了创建新实例的复制性
2)允许动态增加或者减少产品类。
缺点:
1)每个类必须配备一个克隆方法
2)对于新建的类使用拷贝很容易,对于已有的类进行拷贝时,特别当一个类引用不支持串行化的间接对象或引用含有循环结构的时候。
适用场景:
原型模式是在内存二进制流的拷贝,比直接new一个对象性能好很多,特别是需要大量对象时,可以考虑使用原型模式。
类实例化需要消耗非常多的资源时或者实例化需要复杂的数据准备访问权限时。