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

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

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

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

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

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

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

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

 

一,原型模式  

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

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

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

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

  这个图是原型模型的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实现原型模式:

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

 

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

 

  

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

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

 

二,深复制与浅复制

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

 

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

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(); }

  

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

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

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

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

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

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

 

三,深复制的实现

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

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

 

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();
    }
}

 

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

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

 

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

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

  深复制和浅复制涉及的场合还是比较多的,比如数据集对象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设计模式笔记(二) 程序世界里的复印机-原型模式与浅复制/深复制

  未完待续......

你可能感兴趣的:(设计模式)