原型模式:就是从一个对象再创建另一个可定制的对象,而且不需要知道任何创建的细节。
原型模式中的角色:
抽象原型类(Abstract Prototype):提供一个克隆接口
具体原型类(Concrete Prototype): 及实现了克隆接口的具体原型类
原型模式的必要条件:
重写抽象类中的Clone方法或者重写ICloneable中的Clone方法。
也就是说:要实现克隆的这个类,必须实现其Clone方法。
废话不多说:上例子,我这里参考《大话设计模式》中简历的例子:
首先,正常来实现:
简历类:Resume.cs
using System;
namespace Prototypes
{
public class Resume
{
/// <summary>
/// 名字
/// </summary>
public string name;
/// <summary>
/// 性别
/// </summary>
public string sex;
/// <summary>
/// 名字
/// </summary>
public string age;
/// <summary>
/// 工作时间
/// </summary>
public string timeArea;
/// <summary>
/// 公司名称
/// </summary>
public string company;
/// <summary>
/// 构造函数
/// </summary>
public Resume(string name)
{
this.name = name;
}
/// <summary>
/// 设置个人信息
/// </summary>
public void SetPersonalInformation(string age,string sex)
{
this.age = age;
this.sex = sex;
}
/// <summary>
/// 设置工作经历
/// </summary>
public void SetWorkExperience(string timeArea, string company)
{
this.timeArea = timeArea;
this.company = company;
}
public void Show()
{
// 输出个人信息
Console.WriteLine(name+" "+age+" "+sex);
// 输出工作经历
Console.WriteLine("工作经历:"+timeArea+" "+company);
}
}
}
客户端Program.cs
using System;
namespace Prototypes
{
class Program
{
static void Main(string[] args)
{
Resume one = new Resume("camellia");
one.SetPersonalInformation("23","男");
one.SetWorkExperience("1999-2005","领航科技");
one.Show();
Console.WriteLine("--------------------------------------------------");
Resume two = new Resume("camellia");
two.SetPersonalInformation("23", "男");
two.SetWorkExperience("2005-2008", "启蒙科技");
two.Show();
Console.ReadKey();
}
}
}
这样写就很麻烦~如果需要改动,那就需要改好多。
下边我们使用原型模式来实现这个例子:
克隆分浅复制与深复制两种。
浅复制:将原来对象中的所有字段逐个复制到一个新对象,如果字段是值类型,则简单地复制一个副本到新对象,改变新对象的值类型字段不会影响原对象;如果字段是引用类型,则复制的是引用,改变目标对象中引用类型字段的值将会影响原对象。例如, 如果一个对象有一个指向引用类型(如例子中的工作经历)的字段, 并且我们对该对象做了一个浅复制, 那麽两个对象将引用同一个引用(即同一段工作经历)。
我这里测试的代码使用的是C#:C#未我们浅复制提供了一个方法MemberwiseClone();这个方法是这样的,如果字段是值类型,直接复制,如果字段是引用类型,复制引用,但不复制引用的对象。这个需要注意。
上代码:浅复制
简历类:ResumeQian.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Prototypes
{
/// <summary>
/// 实现了ICloneable接口的简历类(浅复制)
/// </summary>
public class ResumeQian : ICloneable
{
public ResumeQian()
{
mWorkExperience = new WorkExperienceQian();
}
private string mName;
private string mSex;
private int mAge;
private WorkExperienceQian mWorkExperience;
public string Name
{
get { return mName; }
set { mName = value; }
}
public string Sex
{
get { return mSex; }
set { mSex = value; }
}
public int Age
{
get { return mAge; }
set { mAge = value; }
}
/// <summary>
/// 关联了一个引用类型
/// </summary>
public WorkExperienceQian WorkExperience
{
get { return mWorkExperience; }
}
public void SetWorkExperience(DateTime startDate, DateTime endDate, string company, string position)
{
this.mWorkExperience.Company = company;
this.mWorkExperience.EndDate = endDate;
this.mWorkExperience.StartDate = startDate;
this.mWorkExperience.Position = position;
}
/// <summary>
/// 展示工作经历
/// </summary>
public void Show()
{
Console.WriteLine("公司:" + this.mWorkExperience.Company);
Console.WriteLine("结束时间:" + this.mWorkExperience.EndDate);
Console.WriteLine("开始时间:" + this.mWorkExperience.StartDate);
Console.WriteLine("位置:" + this.mWorkExperience.Position);
Console.WriteLine("-----------------------------------------------------");
}
/// <summary>
/// 实现ICloneable接口的Clone方法
/// </summary>
/// <returns></returns>
public object Clone()
{
// .Net 为我们提供的浅复制对象的方法
return this.MemberwiseClone();
}
}
}
工作经历类:WorkExperienceQian.cs(引用类型)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Prototypes
{
/// <summary>
/// 工作经历类(浅复制)
/// </summary>
public class WorkExperienceQian
{
public DateTime StartDate;
public DateTime EndDate;
public string Company;
public string Position;
}
}
客户端调用:Program.cs
Console.WriteLine("浅复制=================================================================");
// 实例化简历类
ResumeQian myFirstResume = new ResumeQian
{
Age = 29,
Name = "camellia",
Sex = "男",
};
// 设置第一份工作经历
myFirstResume.SetWorkExperience(new DateTime(2006, 7, 1), new DateTime(2007, 7, 1), "第一", "领航科技");
// 展示第一份简历
myFirstResume.Show();
// 克隆第一份简历为第二份工作经历。
ResumeQian mySecondResume = (ResumeQian)myFirstResume.Clone();
// 设置工作经历
mySecondResume.SetWorkExperience(new DateTime(2007, 8, 1), new DateTime(2008, 8, 1), "第二", "启蒙科技");
// 展示第一份简历
myFirstResume.Show();
// 展示第二份简历,这个时候你会发现一个问题。这里展示的第一份简历的工作经历和第二份简历的工作经历是相同的。
// 因为,我们这里使用的是浅复制,工作经历是引用类型,所以会随着传入的改变而改变。
mySecondResume.Show();
ResumeQian myThirdResume = (ResumeQian)myFirstResume.Clone();
mySecondResume.SetWorkExperience(new DateTime(2008, 8, 1), new DateTime(2009, 8, 1), "第三", "如是科技");
myFirstResume.Show();
mySecondResume.Show();
mySecondResume.Show();
输出效果如下图所示:
如果简历这样复制的话,就会有问题。这个时候我们就用到深复制来实现简历的克隆~
深复制:与浅复制不同之处在于对引用类型的处理,深复制将新对象中引用类型字段指向复制过的新对象,改变新对象中引用的任何对象,不会影响到原来的对象中对应字段的内容。例如,如果一个对象有一个指向引用类型(如例子中的工作经历)的字段,并且对该对象做了一个深复制的话.我门将创建一个新的对象(即新的工作经历)。
上代码:深复制
深复制的精髓就是引用类型的对象也要实现Clone方法。在你要克隆的类中,将引用类型的类科隆到其中。说的不是很清楚,看代码就好了。
简历类:ResumeShen.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Prototypes
{
/// <summary>
/// 实现了ICloneable接口的简历类(深复制)
/// </summary>
public class ResumeShen : ICloneable
{
public ResumeShen()
{
mWorkExperience = new WorkExperienceShen();
}
// ============================ 深复制重点 ================================
/// <summary>
/// 这里使用一个私有的构造函数来对其连接到的引用类型进行复制
/// </summary>
/// <param name="workExperience"></param>
private ResumeShen(WorkExperienceShen workExperience)
{
this.mWorkExperience = (WorkExperienceShen)workExperience.Clone();
}
// ============================ 深复制重点 ================================
private string mName;
private string mSex;
private int mAge;
private WorkExperienceShen mWorkExperience;
public string Name
{
get { return mName; }
set { mName = value; }
}
public string Sex
{
get { return mSex; }
set { mSex = value; }
}
public int Age
{
get { return mAge; }
set { mAge = value; }
}
public WorkExperienceShen WorkExperience
{
get { return mWorkExperience; }
}
/// <summary>
/// 设置功过经历
/// </summary>
/// <param name="startDate"></param>
/// <param name="endDate"></param>
/// <param name="company"></param>
/// <param name="position"></param>
public void SetWorkExperience(DateTime startDate, DateTime endDate, string company, string position)
{
this.mWorkExperience.Company = company;
this.mWorkExperience.EndDate = endDate;
this.mWorkExperience.StartDate = startDate;
this.mWorkExperience.Position = position;
}
public void Show()
{
Console.WriteLine("公司:" + this.mWorkExperience.Company);
Console.WriteLine("结束时间:" + this.mWorkExperience.EndDate);
Console.WriteLine("开始时间:" + this.mWorkExperience.StartDate);
Console.WriteLine("位置:" + this.mWorkExperience.Position);
Console.WriteLine("-----------------------------------------------------");
}
/// <summary>
/// 实现ICloneable接口的Clone方法
/// </summary>
/// <returns></returns>
public object Clone()
{
// 这里不再使用MemberwiseClone方法进行复制了,而是新创建了一个全新的简历。它完全是在内部实现的,外部不用关心它的实现
ResumeShen newResume = new ResumeShen(this.mWorkExperience);
newResume.mSex = this.mSex;
newResume.mName = this.mName;
newResume.mAge = this.mAge;
return newResume;
}
}
}
工作经历类:WorkExperienceShen.cs 也要实现Clone方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Prototypes
{
public class WorkExperienceShen : ICloneable
{
public DateTime StartDate;
public DateTime EndDate;
public string Company;
public string Position;
// ============================ 深复制重点 ================================
public object Clone()
{
// 使用.Net 为我们提供的浅复制对象的方法
// 因为这里已经没有引用对象了(string虽然是引用类型,但C#为我们做了特别处理,可以像值类型一样使用它)。
return this.MemberwiseClone();
}
// ============================ 深复制重点 ================================
}
}
客户端调用:Program.cs
Console.WriteLine("深复制==================================================================");
ResumeShen myFirstResumeShen = new ResumeShen
{
Age = 29,
Name = "camellia",
Sex = "男",
};
myFirstResumeShen.SetWorkExperience(new DateTime(2006, 7, 1), new DateTime(2007, 7, 1), "第一", "领航科技");
myFirstResumeShen.Show();
ResumeShen mySecondResumeShen = (ResumeShen)myFirstResumeShen.Clone();
mySecondResumeShen.SetWorkExperience(new DateTime(2007, 8, 1), new DateTime(2008, 8, 1), "第二", "启蒙科技");
myFirstResumeShen.Show();
mySecondResumeShen.Show();
ResumeShen myThirdResumeShen = (ResumeShen)myFirstResumeShen.Clone();
myThirdResumeShen.SetWorkExperience(new DateTime(2008, 8, 1), new DateTime(2009, 8, 1), "第三", "如是科技");
myFirstResumeShen.Show();
mySecondResumeShen.Show();
myThirdResumeShen.Show();
实现效果如下图所示:
这个效果正是我们想要的。
文章最开始的时候,有说过原型模式的必要条件:
重写抽象类中的Clone方法或者重写ICloneable中的Clone方法。
上边的例子,我们使用了重写ICloneable中的Clone方法。下边我们写一个重写抽象类中的Clone方法的小例子(只有浅复制)
颜色类:Colors.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Prototype
{
// ================ 浅复制 =================
public abstract class Colors
{
public int red;
public int blue;
public int black;
public abstract Colors clone();
}
// ================ 浅复制 =================
}
子类GetRed.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Prototype
{
public class GetRed:Colors
{
public override Colors clone()
{
// MemberwiseClone C# 为我们提供的浅复制对象的方法
return (Colors)this.MemberwiseClone();
}
}
}
客户端调用:Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Prototype
{
class Program
{
static void Main(string[] args)
{
// ================ 浅复制 =================
Colors getRed = new GetRed();
getRed.red = 123;
Console.WriteLine("red:"+ getRed.red);
Colors getRedTwo = getRed.clone();
getRedTwo.red = 152;
Console.WriteLine("redtwo:" + getRedTwo.red);
Console.WriteLine("red:" + getRed.red);
// ================ 浅复制 =================
Console.ReadLine();
}
}
}
运行结果如下:
我们发现,在我们修改getRed对象的Red属性值,没有对getRedTwo的属性产生影响。
即对象副本的修改不会影响对象本身的状态。
主要还是要注意,原型模式不一定非要重写ICloneable中的Clone方法才可以实现。
最后我们总结一下原型模式的优缺点:
优点:
-
隐藏了对象的创建细节,对有些初始化需要占用很多资源的类来说,对性能也有很大提高。
-
在需要新对象时,可以使用Clone来快速创建创建一个,而不用使用new来构建。
缺点:
-
每一个类都需要一个Clone方法,而且必须通盘考虑。
-
对于深拷贝来说,每个关联到的类型都必许实现IClonable接口,并且每增加或修改一个字段是都需要更新Clone方法。
适用场景:
-
类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等
-
通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式
-
一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
有好的建议,请在下方输入你的评论。
欢迎访问个人博客
https://guanchao.site
欢迎访问小程序: