文章出處

前言

設計模式目錄:

本篇目錄:

  其實說到原型模式,大家可能會想到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     }
View Code

  測試代碼:

 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.HeartTypebaby2.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.HeartTypebaby2.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.HeartTypebaby2.HeartType的引用地址是不一樣的,和上面重新賦值對象的效果是一樣的,但是我們調用的時候沒有做額外的操作,就可以實現此效果,但是序列化和反序列化是比較耗時的,這點也需要注意下。

  示例代碼下載:Prototype.rar

后記

  關于創建型模式上面幾篇說的差不多,還有個針對工廠方法模式出現問題的解決方案,下面就是結構型模式了,還在學習中,未完待續。。。

 


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()