1. 程式人生 > >C#設計模式之五原型模式(Prototype Pattern)【創建型】

C#設計模式之五原型模式(Prototype Pattern)【創建型】

tom method 權限 type() 技術 cto 具體類 方便 logs

原文:C#設計模式之五原型模式(Prototype Pattern)【創建型】

一、引言

在開始今天的文章之前先說明一點,歡迎大家來指正。很多人說原型設計模式會節省機器內存,他們說是拷貝出來的對象,這些對象其實都是原型的復制,不會使用內存。我認為這是不對的,因為拷貝出來的每一個對象都是實際存在的,每個對象都有自己的獨立內存地址,都會被GC回收。如果就淺拷貝來說,可能會公用一些字段,深拷貝是不會的,所以說原型設計模式會提高內存使用率,不一定。具體還要看當時的設計,如果拷貝出來的對象緩存了,每次使用的是緩存的拷貝對象,那就另當別論了,再說該模式本身解決的不是內存使用率的問題。
現在說說原型模式的要解決的問題吧,在軟件系統中,當創建一個類的實例的過程很昂貴或很復雜,並且我們需要創建多個這樣類的實例時,如果我們用new操作符去創建這樣的類實例,這就會增加創建類的復雜度和創建過程與客戶代碼復雜的耦合度。如果采用工廠模式來創建這樣的實例對象的話,隨著產品類的不斷增加,導致子類的數量不斷增多,也導致了相應工廠類的增加,維護的代碼維度增加了,因為有產品和工廠兩個維度了,反而增加了系統復雜程度,所以在這裏使用工廠模式來封裝類創建過程並不合適。由於每個類實例都是相同的,這個相同指的是類型相同,但是每個實例的狀態參數會有不同,如果狀態數值也相同就沒意義了,有一個這樣的對象就可以了。當我們需要多個相同的類實例時,可以通過對原來對象拷貝一份來完成創建,這個思路正是原型模式的實現方式。

二、原型模式的詳細介紹



2.1、動機(Motivate)

在軟件系統中,經常面臨著“某些結構復雜的對象”的創建工作;由於需求的變化,這些對象經常面臨著劇烈的變化,但是它們卻擁有比較穩定一致的接口。如何應對這種變化?如何向“客戶程序(使用這些對象的程序)”隔離出“這些易變對象”,從而使得“依賴這些易變對象的客戶程序”不隨著需求改變而改變?

2.2、意圖(Intent)

使用原型實例指定創建對象的種類,然後通過拷貝這些原型來創建新的對象。 --《設計模式》Gof

2.3、結構圖(Structure)

技術分享圖片

2.4、模式的組成

可以看出,在原型模式的結構圖有以下角色:

(1)、原型類(Prototype)
:原型類,聲明一個Clone自身的接口;

  (2)、具體原型類(ConcretePrototype):實現一個Clone自身的操作。

  在原型模式中,Prototype通常提供一個包含Clone方法的接口,具體的原型ConcretePrototype使用Clone方法完成對象的創建。

2.5 原型模式的具體實現

《大話西遊之大聖娶親》這部電影,沒看過的人不多吧,裏面有這樣一個場景。牛魔王使用無敵牛虱大戰至尊寶,至尊寶的應對之策就是,從腦後把下一撮猴毛,吹了口仙氣,無數猴子猴孫現身,來大戰牛魔王的無敵牛虱。至尊寶的猴子猴孫就是該原型模式的最好體現。至尊寶創建自己的一個副本,不用還要重新孕育五百年,然後出世,再學藝,最後來和老牛大戰,估計黃花菜都涼了。他有3根救命猴毛,輕輕一吹,想要多少個自己就有多少個,方便,快捷。

 1 /// <summary>
 2 /// 原型設計模式,每個具體原型是一類對象的原始對象,通過每個原型對象克隆出來的對象也可以進行設置,在原型的基礎之上豐富克隆出來的對象,所以要設計好抽象原型的接口
 3 /// </summary>
 4 namespace 設計模式之原型模式
 5 {
 6     /// <summary>
 7     /// 客戶類
 8     /// </summary>
 9     class Customer
10     {
11         static void Main(string[] args)
12         {
13             Prototype xingZheSun = new XingZheSunPrototype().Clone();
14             Prototype xingZheSun2 = new XingZheSunPrototype().Clone();
15             Prototype xingZheSun3 = new XingZheSunPrototype().Clone();
16 
17             Prototype sunXingZhe = new SunXingZhePrototype().Clone();
18             Prototype sunXingZhe2 = new SunXingZhePrototype().Clone();
19             Prototype sunXingZhe3 = new SunXingZhePrototype().Clone();
20             Prototype sunXingZhe4 = new SunXingZhePrototype().Clone();
21             Prototype sunXingZhe5 = new SunXingZhePrototype().Clone();
22 
23             //1號孫行者打妖怪
24             sunXingZhe.Fight();
25             //2號孫行者去化緣
26             sunXingZhe2.BegAlms();
27 
28             //戰鬥和化緣也可以分類,比如化緣,可以分:水果類化緣,飯食類化緣;戰鬥可以分為:天界寵物下界成妖的戰鬥,自然修煉成妖的戰鬥,大家可以自己去想吧,原型模式還是很有用的
29 
30             Console.Read();
31         }
32     }
33 
34     /// <summary>
35     /// 抽象原型,定義了原型本身所具有特征和動作,該類型就是至尊寶
36     /// </summary>
37     public abstract class Prototype
38     {
39         // 戰鬥--保護師傅
40         public abstract void Fight();
41         // 化緣--不要餓著師傅
42         public abstract void BegAlms();
43 
44         // 吹口仙氣--變化一個自己出來
45         public abstract Prototype Clone();
46     }
47 
48     /// <summary>
49     /// 具體原型,例如:行者孫,他只負責化齋飯食和與天界寵物下界的妖怪的戰鬥
50     /// </summary>
51     public sealed class XingZheSunPrototype:Prototype
52     {
53         // 戰鬥--保護師傅--與自然修煉成妖的戰鬥
54         public override void Fight()
55         {
56             Console.WriteLine("騰雲駕霧,各種武藝");
57         }
58         // 化緣--不要餓著師傅--飯食類
59         public override void BegAlms()
60         {
61             Console.WriteLine("什麽都能要來");
62         }
63 
64         // 吹口仙氣--變化一個自己出來
65         public override Prototype Clone()
66         {
67             return (XingZheSunPrototype)this.MemberwiseClone();
68         }
69     }
70 
71     /// <summary>
72     /// 具體原型,例如:孫行者,他只負責與自然界修煉成妖的戰鬥和化齋水果
73     /// </summary>
74     public sealed class SunXingZhePrototype : Prototype
75     {
76         // 戰鬥--保護師傅-與天界寵物戰鬥
77         public override void Fight()
78         {
79             Console.WriteLine("騰雲駕霧,各種武藝");
80         }
81         // 化緣--不要餓著師傅---水果類
82         public override void BegAlms()
83         {
84             Console.WriteLine("什麽都能要來");
85         }
86 
87         // 吹口仙氣--變化一個自己出來
88         public override Prototype Clone()
89         {
90             return (SunXingZhePrototype)this.MemberwiseClone();
91         }
92     }
93 }


上面代碼中都有詳細的註釋代碼,這裏就不過多解釋。

三、原型模式的實現要點:

Prototype模式同樣用於隔離類對象的使用者和具體類型(易變類)之間的耦合關系,它同樣要求這些“易變類”擁有“穩定的接口”。

  Prototype模式對於“如何創建易變類的實體對象”(創建型模式除了Singleton模式以外,都是用於解決創建易變類的實體對象的問題的)采用“原型克隆”的方法來做,它使得我們可以非常靈活地動態創建“擁有某些穩定接口”的新對象——所需工作僅僅是註冊一個新類的對象(即原型),然後在任何需要的地方不斷地Clone。

  Prototype模式中的Clone方法可以利用.NET中的Object類的MemberwiseClone()方法或者序列化來實現深拷貝。

3.1】、原型模式的優點:

(1)、原型模式向客戶隱藏了創建新實例的復雜性

(2)、原型模式允許動態增加或較少產品類。

(3)、原型模式簡化了實例的創建結構,工廠方法模式需要有一個與產品類等級結構相同的等級結構,而原型模式不需要這樣。

(4)、產品類不需要事先確定產品的等級結構,因為原型模式適用於任何的等級結構

3.2】、原型模式的缺點:

(1)、每個類必須配備一個克隆方法

(2)、配備克隆方法需要對類的功能進行通盤考慮,這對於全新的類不是很難,但對於已有的類不一定很容易,特別當一個類引用不支持串行化的間接對象,或者引用含有循環結構的時候。

3.3】、原型模式使用的場景:

(1)、資源優化場景

類初始化需要消化非常多的資源,這個資源包括數據、硬件資源等。

(2)、性能和安全要求的場景

通過new產生一個對象需要非常繁瑣的數據準備或訪問權限,則可以使用原型模式。

(3)、一個對象多個修改者的場景

一個對象需要提供給其他對象訪問,而且各個調用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調用者使用。

在實際項目中,原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過clone的方法創建一個對象,然後由工廠方法提供給調用者。

四、.NET 中原型模式的實現

在.NET中,微軟已經為我們提供了原型模式的接口實現,該接口就是ICloneable,其實這個接口就是抽象原型,提供克隆方法,相當於與上面代碼中Prototype抽象類,其中的Clone()方法來實現原型模式,如果我們想我們自定義的類具有克隆的功能,首先定義類實現ICloneable接口的Clone方法。其實在.NET中實現了ICloneable接口的類有很多,如下圖所示(圖中只截取了部分,可以用ILSpy反編譯工具進行查看):

namespace System
{
    [ComVisible(true)]
    public interface ICloneable
    {
        object Clone();
    }
}

在Net的FCL裏面實現ICloneable接口的類如圖,自己可以去查看每個類自己的實現,在此就不貼出來了。

技術分享圖片

五、總結

到今天為止,所有的創建型設計模式就寫完了。學習設計模式應該是有一個循序漸進的過程,當我們寫代碼的時候不要一上來就用什麽設計模式,而是通過重構來使用設計模式。創建型的設計模式寫完了,我們就總結一下。Singleton單件模式解決的是實體對象個數的問題。除了Singleton之外,其他創建型模式解決的都是new所帶來的耦合關系。 Factory Method,Abstract Factory,Builder都需要一個額外的工廠類來負責實例化“易變對象”,而Prototype則是通過原型(一個特殊的工廠類)來克隆“易變對象”。(其實原型就是一個特殊的工廠類,它只是把工廠和實體對象耦合在一起了)。如果遇到“易變類”,起初的設計通常從Factory Method開始,當遇到更多的復雜變化時,再考慮重構為其他三種工廠模式(Abstract Factory,Builder,Prototype)。
 一般來說,如果可以使用Factory Method,那麽一定可以使用Prototype。但是Prototype的使用情況一般是在類比較容易克隆的條件之上,如果是每個類實現比較簡單,都可以只用實現MemberwiseClone,沒有引用類型的深拷貝,那麽就更適合了。

C#設計模式之五原型模式(Prototype Pattern)【創建型】