C#設計模式03-原型模式
好久沒有寫部落格了,因為過年實在太忙了,家裡也沒有網路,今天第一天上班就先寫一篇吧。話不多說,今天要寫的內容是:原型模式,它也是建立型模式中的一種
首先來看定義:使用原型例項指定待建立物件的型別,並且通過複製這個原型來 建立新的物件。
理解定義之前先看一幅圖:
孫悟空可以變出很多個和自己一模一樣的猴子,這裡的孫悟空就是原型類,原型類裡面有一個Clone()方法可以複製他自己。有了這個方法他就可以自我複製出很多小猴子。原型模式和孫悟空自我複製自己這個模式差不多。
接下來我們就來看看原型模式的中的三種角色:
(1)Prototype(抽象原型類):這個類宣告克隆(複製)方法(也就是Clone()方法),是所有具體原型類的父類,它可以是抽象類,介面或者具體的實現類
(2)ConcretePrototype(具體原型類):這個類實現(繼承)抽象原型類,實現(重寫)Clone方法以返回自己的一個克隆物件
(3)Client(客戶端類):這個類通過呼叫Clone方法來克隆物件,返回值為Prototype
在介紹原型模式之前,有兩個概念需要解釋一下,也就是深克隆(深複製)和淺克隆(淺複製)。我們知道,C#中,值型別的資料存放在記憶體棧空間中,而引用型別存放在記憶體堆空間中,並且在棧空間上儲存了該物件的引用地址。執行淺克隆操作時,會複製原型物件本身和原型物件中包含的值型別成員,而引用型別並沒有被複制,僅僅複製了引用型別的地址。對克隆物件中引用型別的操作將直接影響原型物件。 而執行深克隆操作時,不僅複製原型物件本身和原型物件中包含的值型別成員,而且會複製引用型別,所以對克隆物件中引用型別的操作不會影響到原型型別。
C#中Object型別的MemberwiseClone方法可以幫助我們實現淺複製,而要想實現深複製的話,可以通過實現ICloneable介面來手動建立當前物件的副本。
接下來我們先實現淺克隆,因為它比較簡單。
假設現在有一個客戶管理系統,有一個客戶類Customer,類裡面有name欄位表示客戶名稱,age欄位表示客戶年齡,還有一個Adress型別的成員變量表示客戶的地址。
現在通過原型模式來淺複製客戶類。
首先是我們的客戶地址Adress類
namespace 原型模式
{
public class Adress
{
public string province;//客戶所在省份
public string county;//客戶所在縣名稱
//..其他的村,街道等省略
public string postCode;//郵編
}
}
然後是我們的客戶類Customer
namespace 原型模式
{
public class Customer//客戶類(具體原型類)
{
public string name;//客戶姓名
public string Name
{
get { return name; }
set { name = value; }
}
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
public Adress adress;
//使用MemberwiseClone方法實現淺克隆
public Customer Clone()
{
return this.MemberwiseClone() as Customer;
}
}
}
最後是我們的客戶端類
namespace 原型模式
{
class Program
{
static void Main(string[] args)
{
Customer c = new Customer();
Adress adress = new Adress() { province="山西",county="河津", postCode="043300"};
c.Name = "曹瑞鵬";
c.adress = adress;
Customer copy = c.Clone();
Console.WriteLine("客戶物件是否相同:{0}",c==copy);
Console.WriteLine("地址是否相同:{0}", c.adress == copy.adress);
Console.WriteLine("客戶年齡是否相同:{0}", ReferenceEquals(c.Age, copy.Age));
Console.WriteLine("客戶姓名是否相同:{0}", ReferenceEquals(c.Name, copy.Name));
Console.ReadLine();
}
}
}
執行結果如下:
從執行結果上可以清楚的看到,客戶物件本身和客戶年齡這兩個變數都不相同了(也就是他們在記憶體中的地址不一致了)。而作為引用型別的地址和姓名這兩個變數在記憶體中的地址是相同的,所以引用型別並沒有被複制。這個就是淺複製。
Object的ReferenceEquals方法(靜態方法)用於測試兩個引用是否指向同一個類的例項,也就是是否指向同一個地址。
然後我們來實現深克隆,深克隆就是可以通過實現ICloneable介面來手動實現克隆
地址類Adress和上面的一樣,客戶端類和客戶類需要修改一下
namespace 原型模式
{
public class Customer:ICloneable//客戶類(具體原型類)
{
public string name;//客戶姓名
public string Name
{
get { return name; }
set { name = value; }
}
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
public Adress adress;
public object Clone()//深克隆方法
{
Adress adress = new Adress() { province = "北京", county = "海淀", postCode = "002335" };
Customer copy = this.MemberwiseClone() as Customer;
copy.adress = adress;
copy.Name = "曹瑞鵬";
return copy;
}
}
}
namespace 原型模式
{
class Program
{
static void Main(string[] args)
{
Customer c = new Customer();
Customer copy = c.Clone() as Customer;
Console.WriteLine("客戶物件是否相同:{0}",c==copy);
Console.WriteLine("地址是否相同:{0}", c.adress == copy.adress);
Console.WriteLine("客戶年齡是否相同:{0}", ReferenceEquals(c.Age, copy.Age));
Console.WriteLine("客戶姓名是否相同:{0}", ReferenceEquals(c.Name, copy.Name));
Console.ReadLine();
}
}
}
執行結果如下:
從執行結果可以看得出來,客戶姓名和客戶地址都成了false,而在淺複製中他們的比較結果是true。這就說明深複製操作會複製引用型別。
除了可以通過實現ICloneable介面達到深複製之外,還可以通過反射,序列化等方式實現深複製,有興趣的朋友可以自己去查閱相關文件資料或者書籍。
到這裡原型模式就給大家介紹完了,最後簡單介紹一下原型模式的優缺點:
優點:複製物件的代價遠小於建立物件的代價,所以當建立的物件較為複雜時,原型模式可以簡化物件的建立過程
缺點:每個類裡面必學有一個Clone克隆方法,當對現有的類進行改造時,需要修改Clone方法。這違背了開閉原則,擴充套件性也不是很好。同時深克隆的實現會比較麻煩,因為噹噹前物件之間存在多重巢狀的時候,每一層物件都必須支援深克隆。
原型型別的適用環境主要如下:
建立物件的成本較高,並且需要儲存物件的狀態資訊,而這些狀態資訊的變化比較小。
下一篇文章將給大家介紹單例模式