设计模式——原型模式
初识
原型模式(Prototype),用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
在原型模式中,所发动创建的对象通过请求原型对象来拷贝原型对象自己来实现创建过程,当然所发动创建的对象需要知道原型对象的类型。这里也就是说所发动创建的对象只需要知道原型对象的类型就可以获得更多的原型实例对象,至于这些原型对象时如何创建的根本不需要关心。
结构
角色:
-
Prototype类:原型类,声明一个克隆自身的接口
-
ConcretePrototype类:具体原型类,实现一个克隆自身的操作
-
Client:让一个原型克隆自身从而创建一个新的对象
浅复制与深复制
浅复制:使用一个已知实例对新创建实例的成员变量逐个赋值,这个方式被称为浅拷贝。
深复制:当一个类的拷贝构造方法,不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值。
值类型与引用类型
概念:值类型直接存储其值,而引用类型存储对其值的引用。部署:托管堆上部署了所有引用类型。
-
值类型变量声明后,不管是否已经赋值,编译器为其分配内存。
-
引用类型当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。
-
值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。
-
引用类型的对象总是在进程堆中分配(动态分配)。
用代码来区分:
结果:
值类型在栈内分配空间大小因变量类型而异;
引用类型在栈内的空间大小相同;
实现
浅克隆
class Program
{
static void Main(string[] args)
{
Resume a = new Resume("大鸟");
a.SetPersonalInfo("男", "29");
a.SetWorkExperience("1998-2000", "XX公司");
Resume b = (Resume)a.Clone();
b.SetWorkExperience("2000-2003", "YY企业");
Resume c = (Resume)a.Clone();
c.SetPersonalInfo("男", "24");
c.SetWorkExperience("2000-2003", "ZZ企业");
a.Display();
b.Display();
c.Display();
Console.Read();
}
}
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 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()
{
return (Object)this.MemberwiseClone();//创建当前对象的浅表副本
}
}
class WorkExperience//工作经验类
{
private string workDate;
public string WorkDate
{
get { return workDate; }
set { workDate = value; }
}
private string company;
public string Company
{
get { return company; }
set { company = value; }
}
}
深克隆
class Program
{
static void Main(string[] args)
{
Resume a = new Resume("大鸟");
a.SetPersonalInfo("男", "20");
a.SetWorkExperience("1998-2000", "阿里");
Resume b = (Resume)a.Clone();
b.SetWorkExperience("1998-2007", "腾讯");
Resume c = (Resume)a.Clone();
c.SetWorkExperience("1998-2003", "华为");
a.Display();
b.Display();
c.Display();
Console.Read();
}
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();
}
}
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 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.age = this.age;
obj.sex = this.sex;
return obj;
}
}
优、缺点与适用情景
优点:
-
如果创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
-
可以使用深克隆保持对象的状态。
-
原型模式提供了简化的创建结构。
缺点:
-
在实现深克隆的时候可能需要比较复杂的代码。
-
需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。
适用情景:
-
如果创建新对象成本较大,我们可以利用已有的对象进行复制来获得。
-
如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占内存不大的时候,也可以使用原型模式配合备忘录模式来应用。相反,如果对象的状态变化很大,或者对象占用的内存很大,那么采用状态模式会比原型模式更好。
-
需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。