原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
吐槽在先:与其说这是一个模式,倒不如说是一种解决对象克隆的方法。好吧,还是叫做模式专业一点。
场景:我们经常会在代码中使用Clone方法进行对象的克隆,当然我们的类默认是木有这个方法,需要自己实现自ICloneable接口,然后就可以开始自己的克隆了。当然,中间不仅仅是实现这个简单,克隆还分为浅拷贝和深拷贝,等会再说。
模式元素:这个其实真没什么元素,只要你的类实现了iCloneable接口就可以的哦(当然中间涉及到的还是深拷贝和浅拷贝问题哦)。
开始实战:
我们用写简历这个例子来作为我们模式的探讨对象,简历可能会写很多份,因为我们发送多次。
/// <summary> /// 简历类 /// </summary> class Resume:ICloneable { public string Name{get;set;} public string Sex { get; set; } public int Age { get; set; } public string Address { get; set; } public string TimeArea { get; set; } public string Company { get; set; } /// <summary> /// 设置工作经历 /// </summary> /// <param name="timeArea"></param> /// <param name="company"></param> public void SetWorkExperience(string timeArea,string company) { this.TimeArea = timeArea; this.Company = company; } /// <summary> /// 显示个人信息 /// </summary> public void Show() { Console.WriteLine(string.Format( "姓名:{0},性别:{1},年龄:{2}",Name,Sex,Age)); Console.WriteLine(string.Format("工作经历:{0} {1}",TimeArea,Company)); } /// <summary> /// 克隆方法 /// </summary> /// <returns></returns> public object Clone() { return (Resume)this.MemberwiseClone(); } }
实体类很简单,除了几个简单的属性之外还有一个设置工作经历的方法,一个显示个人信息的方法,当然还有我们的克隆方法。
调用如下:
Resume resumeA = new Resume { Name = "Listen", Age = 23, Address = "中国" }; resumeA.SetWorkExperience("2010--2012", "X公司"); Resume resumeB = (Resume)resumeA.Clone(); resumeB.SetWorkExperience("2012--2014", "Y公司"); Resume resumeC = (Resume)resumeB.Clone(); resumeC.SetWorkExperience("2014--2016", "Z公司"); resumeA.Show(); resumeB.Show(); resumeC.Show(); Console.Read();
调用过程也很简单,实例化了三个Resume对象,哦no,是一个对象,然后我们克隆了两份才对,当然要通过我们的Clone方法进行,最后进行展示数据,效果上来:
可以看到除了每次的工作经历不一样,其余的个人信息都是相同的,这就是克隆的好处,他把我们的属性的值都给拷贝了一份(等等此处有问题,不是所有的属性哦,只是值类型而已),赋值给新的对象。
P.S:早就想解释了,this.MemberwiseClone()此方法是干嘛呢,问的好,此方法就是创建当前对象的浅表副本,也就是浅复制。方法是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型,则直接复制字段的值。如果字段是引用类型,则复制引用(也就是只new一个对象)而不复制引用的对象(也就是不赋值引用对象的各个属性);因此原始对象及其副本引用同一个对象。
上述的例子中的属性均为值类型,所以进行克隆是没问题的,现在需求有了改变,将工作经历提取为一个类,作为简历的一个属性存在,如下:
添加工作经历类:
/// <summary> /// 工作经历 /// </summary> 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; } } }
修改简历类:
/// <summary> /// 简历类 /// </summary> class Resume:ICloneable { public string Name{get;set;} public string Sex { get; set; } public int Age { get; set; } public string Address { get; set; } public WorkExperience WorkExperience; public Resume() { this.WorkExperience = new WorkExperience(); } /// <summary> /// 设置工作经历 /// </summary> /// <param name="timeArea"></param> /// <param name="company"></param> public void SetWorkExperience(string timeArea,string company) { WorkExperience.WorkDate = timeArea; WorkExperience.Company = company; } /// <summary> /// 显示个人信息 /// </summary> public void Show() { Console.WriteLine(string.Format( "姓名:{0},性别:{1},年龄:{2}",Name,Sex,Age)); Console.WriteLine(string.Format("工作经历:{0} {1}", WorkExperience.WorkDate, WorkExperience.Company)); } /// <summary> /// 克隆方法 /// </summary> /// <returns></returns> public object Clone() { return (Resume)this.MemberwiseClone(); } }
添加了一个工作经历类,属性还是老样子,同时修改了Resume简历类,删除了工作时间和公司名称,替换的为工作经历类的一个对象;在构造函数中对工作经历类进行初始化,同时修改了设置工作经历中的代码以及显示个人信息的代码,其余木有变化哦。
再次使用:
使用的代码不用变哦.
我勒个去,嘛回事,为神马工作经历都是同一个呢,明明设置了三次,哦啦啦,为神马捏。还是因为MemberwiseClone搞的鬼,刚才说了它复制值类型是直接复制的,而复制引用类型而只是赋值一个引用(即复制一个地址,也就是说我们三个对象的工作经历是同一个地址的对象,我的天呢),所以它记录的永远都是最后一次赋值的结果,好恐怖哦。所以,我们就需要一个新的东西,等等等等,就是“深复制”。
继续修改:
工作经历类:
/// <summary> /// 工作经历 /// </summary> 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 (WorkExperience)this.MemberwiseClone(); } }
给工作经历类同样实现了ICloneable接口,并且实现了Clone方法,可是实现了这个方法谁来调用呢,问的好,继续修改。
简历类修改:
/// <summary> /// 克隆方法 /// </summary> /// <returns></returns> public object Clone() { Resume resume = new Resume(); resume.Name = this.Name; resume.Sex = this.Sex; resume.Age = this.Age; resume.WorkExperience = (WorkExperience)this.WorkExperience.Clone(); return resume; }
再次调用:
同样不需修改。
哦好开心,因为简历类只修改了Clone方法,在克隆的时候手动new一个对象,然后把乱七八糟的属性一一赋值,最后把工作经历类通过自己的克隆得到,然后返回克隆的简历对象,这样既正确了哦。
克隆无处不在,比如DataSet既有Copy方法,也有Clone方法,Clone只会复制结构,而Copy则会把结构和数据都给复制过去。
好了,这次就到这里了,源码在此,欢迎大家多提意见和建议。