定义
原型模式(Prototype Pattern):使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
如何使用
在使用原型模式时,我们需要首先创建一个原型对象,再通过复制(克隆,拷贝)这个原型对象来创建更多同类型的对象,也就是通过复制原型对象来得到更多同类型的对象。
//实例化一次
Resume a = new Resume("大鸟");
a.SetPersonalInfo("男", "29");
a.SetWorkExperience("1998-2000", "XX公司");
//然后只需要调用Clone()方法就可以实现新简历的生成,并且可以修改新简历的细节
Resume b = (Resume)a.Clone();
b.SetWorkExperience("1998-2006", "YY企业");
为什么使用克隆
原型模式本身就是用来解决系统中需要创建一个新对象,而要创建的对象和系统中已有对象存在诸多重复的属性和方法,如果再去new一个新的空对象,就需要对新的对象进行赋值初始化操作,这会增加不必要的工作量。使用克隆的好处就是可以将已有对象的属性和方法直接复制给新的对象。
不使用Clone()方法代码如下:需要几份简历,就实例化几次,而每New一次,都需要执行一次构造函数,如果构造函数的执行时间很长,那么多次执行这个初始化操作就太低效了。
Resume a = new Resume("大鸟");
a.SetPersonalInfo("男", "29");
a.SetWorkExperience("1998-2000", "XX公司");
Resume b = new Resume("大鸟");
b.SetPersonalInfo("男", "29");
b.SetWorkExperience("1998-2000", "XX公司");
而使用Clone()方法的代码如下:
//实例化一次
Resume a = new Resume("大鸟");
a.SetPersonalInfo("男", "29");
a.SetWorkExperience("1998-2000", "XX公司");
/然后只需要调用Clone()方法就可以实现新简历的生成,并且可以修改新简历的细节
Resume b = (Resume)a.Clone();
b.SetWorkExperience("1998-2006", "YY企业");
类图
角色
Prototype(抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类。但是对于.net而言,抽象原型类是用不着的,因为.net在system命名空间中提供了ICloneable接口,其中就是唯一的一个方法Clone()。
ConcretePrototype(具体原型类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
//简历
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();
}
//提供Clone方法调用的私有构造函数,以便克隆“工作经历”的数据
private Resume(WorkExperience work)
{
this.work = (WorkExperience)work.Clone();
}
//设置个人信息
public void SetPersonalInfo(string sex, string age)
{
this.sex = sex;
this.age = age;
}
//设置工作经历
public void SetWorkExperience(string workDate, string company)
{
work.WorkDate = workDate;
work.Company = company;
}
//显示
public void Display()
{
Console.WriteLine("{0} {1} {2}", name, sex, age);
Console.WriteLine("工作经历:{0} {1}", work.WorkDate, 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;
}
}
//工作经历实现ICloneable接口
class WorkExperience : ICloneable
{
private string workDate;
public string WorkDate
{
get { return workDate; }
set { workDate = value; }
}
private string company;
public string Company
{
get { return company; }
set { company = value; }
}
//工作经历类实现克隆方法
public Object Clone()
{
return (Object)this.MemberwiseClone();
}
}
Client(客户类):让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。
static void Main(string[] args)
{
//实例化一次
Resume a = new Resume("大鸟");
a.SetPersonalInfo("男", "29");
a.SetWorkExperience("1998-2000", "XX公司");
//然后只需要调用Clone()方法就可以实现新简历的生成,并且可以修改新简历的细节
Resume b = (Resume)a.Clone();
b.SetWorkExperience("1998-2006", "YY企业");
Resume c = (Resume)a.Clone();
c.SetWorkExperience("1998-2003", "ZZ企业");
a.Display();
b.Display();
c.Display();
Console.Read();
}
运行结果
以上代码实现结果属于深复制。
深克隆:无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
浅克隆:如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
详细理解深复制和浅复制,请参考:点击打开链接
总结
优点
- 简化对象的创建,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。
- 使用原型模式创建对象比直接new一个对象在性能上要好的多,特别是复制大对象时,性能的差别非常明显。
缺点
- 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了“开闭原则”。
- 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。
适用情况
- 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。
- 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。
- 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。