• 小菜学习设计模式(四)—原型(Prototype)模式


    from:http://www.cnblogs.com/xishuai/p/3527083.html

    本篇目录:

      其实说到原型模式,大家可能会想到Clone,其实也不尽然,在我们的日常生活中,原型(Prototype)模式也可以经常看到:

    你觉得某位明星的发型很好看,你也想做个和他一样的发型,于是你拿个照片去理发店说:我要做个和这个一模一样的发型。

      其实那个明星的发型某种意义上说就是原型,因为已经有发型参考了,所以发型师会很快的做出来。就像我们在编程时,当创建一些对象(特别是大对象)非常耗时时,或者创建过程非常复杂时,原型模式就会很有用。

    概述

      我们看下GoF对原型(Prototype)模式下的定义:

    使用原型模型实例指定将要创建的对象类型,通过复制这个实例创建新的对象。

      从这个定义中,我们可以分解下,其实说的意思就是:指定类型,复制对象。就像上面的例子,造型就是一个指定的类型,然后去复制此类型的对象。原型模型的简单静态类图:

      Client使用Prototype的Clone()方法得到这个对象的副本,我们先看下引用类型的存储:引用类型的变量在栈中分配一个内存空间,这个内存空间包含的是对另一个内存位置的引用,这个位置是托管堆中的一个地址,即存放此变量实际值的地方。.NET自动维护一个堆指针,它包含堆中下一个可用内存空间的地址。就是说引用类型在栈中存储一个托管堆的地址,托管堆存储这个变量的实际值。上面所说的“复制对象”在原型(Prototype)模式中分为两种,一种是只复制栈中的托管堆地址,就是说副本对象和原对象指向的托管堆的地址是一样的,这种复制称为浅拷贝(Shallow Copy);另一种是栈中存放的地址和托管堆中实际值都复制,就是说生成一个和原对象一样的全新对象,这种复制称为深拷贝(Deep Copy)。

      我们用一张示意图可以简单概述下:

      其实关于浅拷贝和深拷贝在我们现实生活中也有相应类似的例子,比如双胞胎,长的一样就像一个模子刻出来的一样,而且各自有各自的身体互不影响,在某种意义上说可以称为深拷贝;在双胞胎中也有一些特殊的,比如连体双胞胎,有的两个身体公用一个心脏什么的,怎么说呢,在某种意义上这种你也可以称为浅拷贝。这个例子只是形象说明下原型模式中复制对象的含义,可能有些不恰当的地方。

    实现

      其实在.net中关于浅拷贝提供了一个方法MemberwiseClone();

      MemberwiseClone()方法返回的是Object类型,注意方法的修饰符是protected,就是说,如果想让外部对象使用它,必须在子类重写该方法,设定其访问范围是public。还有就是实现对象的复制必须实现ICloneable接口,并实现其Clone()方法。就上面说的双胞胎的例子,我们做一个简单的示例:

    复制代码
     1     /// <summary>
     2     /// 心脏类
     3     /// </summary>
     4     public class Heart
     5     {
     6         private int _size;
     7         private int _volume;
     8         /// <summary>
     9         /// 大小
    10         /// </summary>
    11         public int Size
    12         {
    13             get { return _size; }
    14             set { _size = value; }
    15         }
    16         /// <summary>
    17         /// 体积
    18         /// </summary>
    19         public int Volume
    20         {
    21             get { return _volume; }
    22             set { _volume = value; }
    23         }
    24     }
    25 
    26     /// <summary>
    27     /// baby类
    28     /// </summary>
    29     public class Baby : ICloneable
    30     {
    31         private string _name;
    32         private string _description;
    33         private Heart _hearttype;
    34         /// <summary>
    35         /// 名称
    36         /// </summary>
    37         public string Name
    38         {
    39             get { return _name; }
    40             set { _name = value; }
    41         }
    42         /// <summary>
    43         /// 描述
    44         /// </summary>
    45         public string Description
    46         {
    47             get { return _description; }
    48             set { _description = value; }
    49         }
    50         /// <summary>
    51         /// 心脏特征
    52         /// </summary>
    53         public Heart HeartType
    54         {
    55             get { return _hearttype; }
    56             set { _hearttype = value; }
    57         }
    58 
    59         #region ICloneable 成员
    60         public object Clone()
    61         {
    62             return this.MemberwiseClone();
    63         }
    64         #endregion
    65     }
    复制代码

      测试代码:

    复制代码
     1         static void Main(string[] args)
     2         {
     3             Baby baby1 = new Baby();
     4             baby1.Name = "I'm baby1";
     5             baby1.Description = "I'm baby";
     6             baby1.HeartType = new Heart() { Size = 111, Volume = 222 };
     7             Baby baby2 = (Baby)baby1.Clone();
     8             baby2.Name = "I'm baby2";
     9 
    10             Console.WriteLine("baby1 info:");
    11             Console.WriteLine(baby1.Name);
    12             Console.WriteLine(baby1.Description);
    13             Console.WriteLine("baby2 info:");
    14             Console.WriteLine(baby2.Name);
    15             Console.WriteLine(baby2.Description);
    16             Console.WriteLine("The heart of the different:");
    17             Console.WriteLine(baby1.HeartType == baby2.HeartType);
    18         }
    复制代码

      运行结果:

      我们可以看到baby1.HeartType和baby2.HeartType的引用地址是一样的,这种就是浅拷贝(Shallow Copy),就说明baby1和baby2公用一个心脏,是连体双胞胎。

    深拷贝(Deep Copy)

      其实上面的代码稍微修改下就是深拷贝,如下:

    复制代码
     1         static void Main(string[] args)
     2         {
     3             Baby baby1 = new Baby();
     4             baby1.Name = "I'm baby1";
     5             baby1.Description = "I'm baby";
     6             baby1.HeartType = new Heart() { Size = 111, Volume = 222 };
     7             Baby baby2 = (Baby)baby1.Clone();
     8             baby2.HeartType = new Heart() { Size = 111, Volume = 222 };//重新创建对象
     9             baby2.Name = "I'm baby2";
    10 
    11             Console.WriteLine("baby1 info:");
    12             Console.WriteLine(baby1.Name);
    13             Console.WriteLine(baby1.Description);
    14             Console.WriteLine("baby2 info:");
    15             Console.WriteLine(baby2.Name);
    16             Console.WriteLine(baby2.Description);
    17             Console.WriteLine("The heart of the different:");
    18             Console.WriteLine(baby1.HeartType == baby2.HeartType);
    19         }
    复制代码

      上面给baby2重新创建一个和baby1一样的心脏,而不是公用一个,运行结果:

      可以看到baby1.HeartType和baby2.HeartType的引用地址是不一样的,虽然是一样的心脏,但是是两个独立相同的心脏,其实上面的方法并不算是深拷贝,只是实现了深拷贝的效果,因为并不是在拷贝中完成的。这种气势有个不好的地方,当一个对象中有很多对象组合的时候,而且这个对象内部很复杂,我们不可能复制完之后,每个对象的去重新赋值,这样实现深拷贝就没有什么意义。当然还有一种实现深拷贝的方式就是序列化,必须在类的前面加上[Serializable]表示,指示这个类是可以序列化的,我们把Clone()的方法修改下:

    复制代码
     1         public object Clone()
     2         {
     3             object result = null;
     4             MemoryStream stream = new MemoryStream();
     5             BinaryFormatter formatter = new BinaryFormatter();
     6             formatter.Serialize(stream, this);
     7             stream.Close();
     8             byte[] streamByte = stream.ToArray();
     9             MemoryStream stream2 = new MemoryStream(streamByte);
    10             result = formatter.Deserialize(stream2);
    11             stream2.Close();
    12             return result;
    13         }
    复制代码

      现在Clone()方法做的工作就是序列化和反序列化,我们使用浅拷贝的测试代码,运行结果为:

      baby1.HeartType和baby2.HeartType的引用地址是不一样的,和上面重新赋值对象的效果是一样的,但是我们调用的时候没有做额外的操作,就可以实现此效果,但是序列化和反序列化是比较耗时的,这点也需要注意下。

      示例代码下载:Prototype.rar

    后记

      关于创建型模式上面几篇说的差不多,还有个针对工厂方法模式出现问题的解决方案,下面就是结构型模式了,还在学习中,未完待续。。。

    作者:田园里的蟋蟀 
    出处:http://www.cnblogs.com/xishuai/ 
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
  • 相关阅读:
    centos7 docker安装awvs
    screen命令简介-实现linux多窗口
    linux命令重命名
    iptables防止nmap扫描
    cobal strike could not start listener address already in use (bind failed)
    docker简介及安装
    「Sqlserver」数据分析师有理由爱Sqlserver之二-像使用Excel一般地使用Sqlserver
    「Sqlserver」数据分析师有理由爱Sqlserver之一-好用的插件工具推荐
    「PowerBI」分析服务多维数据结构重回关系数据库的一大绝招
    「数据分析」Sqlserver中的窗口函数的精彩应用之数据差距与数据岛(含答案)
  • 原文地址:https://www.cnblogs.com/liuqiyun/p/6678874.html
Copyright © 2020-2023  润新知