您的位置:首页 > 其它

JohnConnor设计模式笔记(二) 程序世界里的复印机-原型模式与浅复制/深复制

2012-06-05 11:14 309 查看
-从这篇开始首先会分析的是设计模式中5个创建型模型的一些学习心得,文章最后会附上目录与进度表,希望大家多多支持多多推荐。

  在没有复印机的时代,如果是你跑去西天取经,你觉得佛祖会让你直接把经书拉回家么,咱没金蝉子那么大的面子么....

  必然的结果就是抄书。你抄个十年八载的拉回大唐,原手抄版得保留作为典籍,但为了弘扬大乘佛法,就又得抄书,抄个几十万册然后分发给各大寺院。

  在程序猿眼中,佛祖的经书就是一个对象,抄书就相当与 new 了一个新的对象,然后把经书中的内容填充到新的对象中。

  想想看,我们每抄一本书,就相当与把书中的内容完完整整的读了一遍,无法忽略细节来创建一个对象,也就是说经书的构造函数执行的时间很长,每次都new的话效率实在太低了。

  并且如果你不小心将"自古以来,黄岩岛都是中国的固有领土",抄成“自古以来,菲律宾都是中国的固有领土”<开个玩笑>,那整个大唐都会这么认为了。

  那么多书要改起来,还不得改到猴年马月去了,那时候又没有微博,让你公开道个歉。不过我是支持这么干的,干嘛道歉呢啧啧。。把日本也纳入其中我都木意见。

一,原型模式  

  为了解决类似于这种从一个对象再创建<克隆>另外一个可定制的对象,而不需要知道任何创建的细节的问题,人类世界发明了印刷术和更高级的复印机。

  而我们的原型模式就是程序世界中的复印机。

  我们来具体的看下原型模式(Prototype)的实现:

  


  这个图是原型模型的UML类图,在设计模式中会常用类图来图解其结构。<如对UML类图有疑惑,请移步该系列的第一篇来了解UML类图:传送门>

  基本原型模式的代码

/// <summary>
/// 原型抽象类
/// </summary>
public abstract class Prototype
{
public string Text { get; set; }
public abstract Prototype Clone();//抽象类关键就是有这样一个Clone方法
}
/// <summary>
/// 具体的原型类 经书
/// </summary>
public  class Book : Prototype
{
public override Prototype Clone()
{
return (Prototype)this.MemberwiseClone();//创建当前对象的浅表副本。
}
}


  

  MemberwiseClone()方法是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象,因此,原始对象及副本引用同一个对象。

  这里就引出了深复制与浅复制的问题,这个在稍后探讨。

  这样我们就不必一个一个的抄书了:

static void Main(string[] args)
{
Book original = new Book { Text = "抄死你不偿命的经文" };
Book copy1 = new Book { Text = original.Text };//抄完你已经快往生了....
Book copy2 = (Book)original.Clone();//克隆一下就可以了
Console.WriteLine("copy2.Text is {0}", copy2.Text);
}


  

  通过克隆<现实中可能是刻板印刷>,我们可以不清楚经书的内容,就可以得到一个原件的副本,当我们需要了解它的时候,再去了解它。

  这就是上文提到过的原型模式的意义,一般在初始化的信息不发生变化的情况下,克隆是最好的办法,这既隐藏了对象创建的细节,又大大的提高了性能。

  对于.NET来说,原型模式非常好实现,在System命名空间中提高了ICloneable接口,其中只有唯一的一个方法Clone()。

  这样我们只需要实现这个接口就可以完成原型模式了,原型抽象类Prototyoe已经不需要了:

  .NET实现原型模式:

  


public class Book : ICloneable
{
public string Text { get; set; }
public Object Clone()
{
return (Object)this.MemberwiseClone();
}
}


  

  方法调用方式于上述例子无异。

  好了,原型模式就介绍到这。现在我们回过头来看看之前说的深复制和浅复制的问题。

二,深复制与浅复制

  前文已经介绍过MemberwiseClone()方法是创建浅表副本,对于对象字段为引用类型的,只复制引用,而不复制引用的对象。我们来改一下Book类:

  


public class Book : ICloneable
{
public string Text { get; set; }
public Version Version { get; set; }
public Object Clone()
{
return (Object)this.MemberwiseClone();
}
public void ShowVersion()
{
Console.WriteLine("Version {0} Author is {1}",Version.Num,Version.Author);
}
}
public class Version
{
public int Num { get; set; }
public string Author { get; set; }
}


  

  这种情况下,Book类中就有引用类型的属性Version了。那么在克隆过程中会发生什么事情呢?

static void Main(string[] args)
{
Book original = new Book { Text = "抄死你不偿命的经文", Version = new Version { Num = 0, Author = "如来" } };
Book copy1 = (Book)original.Clone();
Book copy2 = (Book)original.Clone();
Book copy3 = (Book)original.Clone();
copy1.Version.Author = "孙猴子";
copy2.Version.Author = "猪八戒";
copy3.Version.Author = "JohnConnor";
copy1.Version.Num = 1;
copy2.Version.Num = 2;
copy3.Version.Num = 3;
copy1.ShowVersion();
copy2.ShowVersion();
copy3.ShowVersion();
Console.ReadKey();
}


  

  结果并不像我们所想的,每个版本的作者分别为猴子,八戒,和我。

  


  三个版本信息引用的都是最后一次的设置,这是因为上文所述的,只复制引用,而不复制引用的对象,克隆的过程中并没有创建对对象,现在三个引用都指向了同一个Version对象。

  这就叫做浅复制,被复制的对象的所有变量都含有与原来的对象相同的值,但所有的对其他对象的引用都仍然指向原来的对象。

  但这很明显会有问题,比如上述问题,我们需要copy1,copy2,copy3中的Version引用指向不同的对象,才不会出现修改一个全都改变的结果。

  这种叫做深复制,把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。

三,深复制的实现

  以上述抄经为例,代码结构图

  


public class Book : ICloneable
{
public string Text { get; set; }
private Version version;
public Version Version
{
get { return version;}
set { this.version = (Version)value.Clone(); }//Version的Set方法中使用Clone的对象,这样就创建了新的Version对象
}
public Object Clone()//实现Clone方法,对新对象的相关字段赋值,返回一个深赋值的Book对象
{
Book obj = new Book();
obj.Text = this.Text;
obj.Version = this.Version;
return obj;
}
public void ShowVersion()
{
Console.WriteLine("Version {0} Author is {1}", Version.Num, Version.Author);
}
}
public class Version : ICloneable
{
public int Num { get; set; }
public string Author { get; set; }
public Object Clone()//实现Clone方法
{
return (Object)this.MemberwiseClone();
}
}


  这样我们再执行控制台程序输出克隆结果:

  


  这次三个结果各不相同了把。

  不过要注意,我们这里只有一层引用,如果深复制深入的层数过多或出现循环引用就比较麻烦,需要格外的小心。

  深复制和浅复制涉及的场合还是比较多的,比如数据集对象DataSet,它就有两个方法Clone()和Copy(),Clone()用来复制其结构,但不复制数据,实现了原型模式的浅复制。

  Copy()方法在复制结构的基础上,也复制数据,即原型模式的深复制。

  

  最后补充一点

copy1.Text = "波若波罗蜜";
Console.WriteLine(original.Text);
Console.ReadKey();


  

  大家觉得这句会输出什么呢?按照上述的理论,Text是string-引用类型,那所有的对象的Text应该引用的是同一个string对象,改一个就全改了。

  然而这个担心是不必要的,输入会是最开始如来写下的"抄死你不偿命的经文"。

  这里可能有人要跳出来说,string不就是引用类型么,为什么在不需要特殊处理就能完成深复制?

  因为对string对象本身进行赋值的过程是,创建新的string对象来存储新值,返回新对象的引用。这是C# string类型的一个特点。

  <若对string有任何疑问的,或是引用类型和值类型不太了解的,我之前有一篇文章深入内存探讨这个问题,点击传送门进入>

  ----------------------------------------------End--------------------------------------------------

  JohnConnor设计模式笔记系列 目录

  JohnConnor设计模式笔记(一) 学习设计模式之前你必须掌握的-看懂UML类图

  JohnConnor设计模式笔记(二) 程序世界里的复印机-原型模式与浅复制/深复制

  未完待续......
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: