.Net中的設計模式——Factory Method模式

 

一、模式概述

也許Factory Method模式是設計模式中應用最廣泛的模式。在面向對象的設計中,關於對象的管理是其核心所在,而其中對象的創建則是對象管理的第一步。對象的創建非常簡單,在C#中,只需要應用new操作符調用對象的構造函數即可,然而創建對象的時機卻非常重要。

 

首先我們從對象的特徵來看,代表抽象關係的類型,如接口和抽象類,是不能創建的,換句話說,我們要創建的對象都是與具體的對象類型有關。因此,對象的創建工作必然涉及到設計中的實現細節,從而導致創建者與具體的被創建者之間耦合度增強。舉例來說,如果在一個項目中我們需要創建一些圖形對象,例如CircleSquare。這些對象的結構如下:

factory1.GIF
這個結構是非常符合
OO思想的,它通過IShape接口將SquareCircle對象抽象出來,根據多態的原理,我們完全可以在程序中用IShape來代替具體的SquareCircle類,從而將具體的對象類型綁定留到運行時。然而,上文說到,接口對象是不能創建的,因此,項目一旦要創建IShape類型的對象,必然要針對具體的對象SquareCircle進行創建操作。例如:

    IShape shape = new Square();

如果是開發一個圖形工具,諸如SquareCircle之類的對象,其創建工作必然非常頻繁。可以設想,在這個項目的各個模塊中,將會大量充斥着如上的代碼行,導致的結果是各個模塊無法與Square對象結耦,這意味着,如果我們改變創建的對象爲Circle,就需要修改所有調用new Square()操作的模塊。這既加大了工作量,同時也導致了項目的不可擴展性,以及模塊的不可重用性。而對於圖形對象的抽象IShape來說,也是不必要而失敗的。

 

在面向對象的設計中,我們常常將可能變化的操作進行封裝,封裝的內容可能僅是某種行爲,也可能是一種狀態,或者是某些職責。而在當前的案例中,我們需要將對象的創建行爲進行封裝,這就引入了Factory Method模式。此時的對象就是Factory要生產的產品。既然產品有兩種,相對應的工廠也應該是兩個,即SquareFactoryCircleFactory。在Factory Method模式中,工廠對象的結構應與產品的結構平行,並與之一一對應,所以,對這兩個工廠而言,還需要爲其抽象出一個共同的工廠接口IShapeFactory

factory2.GIF
代碼如下:

public interface IShapeFactory

{

    IShape CreateShape();

}

public class SquareFactory:IShapeFactory

{
    public IShape CreateShape()

{

     return new Square();

}

}

public class CircleFactory:IShapeFactory

{
    public IShape CreateShape()

{

     return new Circle();

}

}

通過Factory Method模式,我們完成了對象創建的封裝,將前面諸如IShape shape = new Square()的代碼全部移到了各自的工廠對象中,並放到CreateShape()方法中實現。整個結構如下圖所示:

factory3.GIF
請注意
CreateShape()方法的返回類型是IShape類型,這就有效地避免了工廠對象與具體產品對象的依賴。

也許會有人認爲,雖然通過工廠方法,將創建IShape對象的職責轉交給工廠對象,然而在工廠類的結構中,仍然存在具體的工廠類對象。如此以來,雖然我們解除了模塊與具體Shape對象的依賴,卻增加了對具體工廠對象的依賴,這會帶來何種益處?

讓我們從對象創建的頻率來分析。對於一個圖形工具而言,IShape對象的創建無疑是頻繁的,最大的可能性是在這個項目的各個模塊中都可能存在創建IShape對象的需要。而工廠對象則不盡然,我們完全可以集中在一個模塊中,初始化這個工廠對象,而在需要IShape對象的時候,直接調用工廠實例的CreateShape()就可以達到目的。

舉例來說,假設在圖形工具中,有三個模塊:ModuleAModuleBModuleC;這三個模塊中都需要創建Square對象,則按照原來的設計方案,這三個模塊都包含這樣一行代碼:

IShape shape = new Square();

此時,與Square對象有依賴關係的就包括了ModuleAModuleBModuleC三個模塊。如果我們需要修改shape對象爲Circle類型,則這個變動無疑會影響到上述的三個模塊。現在,我們引入Factory Method模式,並增加一個模塊名爲ModuleFactory,在這個模塊中,我們創建一個工廠對象:

IShapeFactory shapeFactory = new SquareFactory();

如此以來,原來的三個模塊有關Square對象的創建,就相應地修改爲:

IShape shape = shapeFactory.CreateShape();

此時,即使需求發生改變,需要對shape對象進行修改,那麼我們只需要修改ModuleFactory模塊中的代碼:

IShapeFactory shapeFactory = new CircleFactory();

ModuleAModuleBModuleC三個模塊則根本不需要作任何改變。如此的設計改進,雖然在項目中增加了三個工廠對象,並引入了ModuleFactory,但它卻完成了ModuleAModuleBModuleC與具體的Square對象的解耦,從而將這三個模塊與產品對象的依賴性轉嫁到ModuleFactory上。如此以來,牽一髮而不動其全身,極大地提高了模塊的重用性。

從上述的分析可知,引入工廠對象並不是簡單地爲產品建立相應的工廠,而是要注意劃分各個模塊的職責,將工廠對象的創建放到合適的地方。最佳方案莫過於將創建工廠對象的職責集中起來,放到一個模塊中;而不是在需要創建產品時,才創建工廠對象。錯誤的例子是在創建產品時將工廠對象的創建於產品對象的創建放在一起,並分佈在各個模塊中:

IShapeFactory shapeFactory = new SquareFactory();

IShape shape = shapeFactory.CreateShape();

這樣的做法,則引入Factory Method模式,無異於畫蛇添足了。

二、.Net Framework中的Factory Method模式

Factory Method模式在項目設計中應用非常廣泛,在.Net Framework中自然也不例外。例如,在.Net中爲處理Web請求,在框架類庫中提供了基類WebRequest。它能夠通過傳入的Uri對象,創建基於不同協議的Web請求。例如,當Uri的前綴爲“https://”或“http://”時,則返回HttpWebRequest對象,如果是“file://”,則返回FileWebRequest對象。HttpWebRequestFileWebRequest對象是WebRequest的派生類。WebRequest的類結構如圖:

factory4.GIF
如何創建一個
WebRequest的實例呢?我們可以直接調用該類的靜態方法Create()

WebRequest myRequest = WebRequest.Create(“http://www.cnblogs.com”);

然後我們可以根據該WebRequest對象獲得WebResponse

WebResponse myResponse = myRequest.GetResponse();

……

myResponse.Close();

從上面一段代碼來看,Create()靜態方法似乎是簡單工廠模式的一種實現,它可以根據方法傳遞進來的參數判斷WebRequest的類型,創建具體的WebRequest對象,並返回該對象。那麼最簡單的實現方法,就是通過if/else條件判斷參數的類型,以決定創建的對象類型。顯然,這並非一個好的方法,它直接導致的就是WebRequest的具體子類與其靜態方法Create()直接依賴,一旦增加新的WebRequest子類,必然要修改Create()方法。

既然涉及到對象的創建,最好的方式就是使用Factory Method模式。在.Net中,爲創建WebRequest對象提供了一個專門的工廠接口IWebRequestCreate,該接口僅有一個方法,即Create()方法:

public interface IWebRequestCreate

{      

        WebRequest Create(Uri uri);

}

對應不同的Web請求,工廠模式爲其提供了不同的具體工廠類,這些類均實現了IWebRequestCreate接口。例如HttpRequestCreator

internal class HttpRequestCreator : IWebRequestCreate

{

        internal HttpRequestCreator() {}

       

        public WebRequest Create( Uri Uri )

{

            return new HttpWebRequest(Uri);

        }

}

還有類FileRequestCreator

   internal class FileWebRequestCreator : IWebRequestCreate

{

        internal FileWebRequestCreator() {}

 

        public WebRequest Create(Uri uri)

{

            return new FileWebRequest(uri);

        }

}

這些類都實現了接口IWebRequestCreateCreate()方法,返回各自對應的WebRequest對象,即工廠模式所要生產的產品。

這樣,我們就爲產品類建立了一個和其完全一一對應的工廠類結構:

factory5.GIF
    請注意工廠類和產品類之間,只存在接口
IWebRequestCreate和抽象類WebRequest之間的依賴關係,這正是OOP中面向抽象編程的實質。

三、進一步探索

根據前面的描述,當我們在創建WebRequest對象時,需要在系統中預先創建其對應的工廠類對象。如下所示:

IWebRequestCreate webRequestCreate = new HttpRequestCreator();

然而與一般對象不同的是,WebRequest的類型是隨時可能變化的,這就導致其對應的工廠類型也會經常改變。如果將上面的代碼寫到一個專門的模塊中,並供客戶端修改,會缺乏一定的靈活性。並且對於客戶而言,WebRequest對象的創建實在太麻煩了。因爲.Net Framework是一個類庫,而類庫的設計理念,就是要儘可能讓用戶更容易更方便地使用類庫,至於內部的實現細節,用戶是不用理會的。因此,.NetFactory Method模式進行了一些加工。下面,我將對WebReuest的創建過程基於.Net的實現進行深入分析。

前面提到,WebRequest是一個抽象類,但它提供了一個靜態方法Create(),能夠根據方法中傳入的Uri地址,創建相對應的WebRequest對象。它的實現代碼如下:

public static WebRequest Create(Uri requestUri)

{

if (requestUri == null) {

    throw new ArgumentNullException("requestUri");

}

return Create(requestUri, false);

}

該方法實質是調用了WebRequest中的私有靜態方法Create()

private static WebRequest Create(Uri requestUri, bool useUriBase)

{

string LookupUri;

WebRequestPrefixElement Current = null;//

bool Found = false;

 

if (!useUriBase) {

    LookupUri = requestUri.AbsoluteUri;

}

else {

   LookupUri = requestUri.Scheme + ':';

}

 

int LookupLength = LookupUri.Length;

ArrayList prefixList = PrefixList; //

            

for (int i = 0; i < prefixList.Count; i++)

{

     Current = (WebRequestPrefixElement)prefixList[i]; //

    

     // See if this prefix is short enough.

     if (LookupLength >= Current.Prefix.Length)

{

         // It is. See if these match.

         if (String.Compare(Current.Prefix,

               0,

               LookupUri,

               0,

               Current.Prefix.Length,

               true,

               CultureInfo.InvariantCulture) == 0)

{

               Found = true;

               break;

         }

      }

}

   if (Found)

{

      return Current.Creator.Create(requestUri); //

  }

 

  throw new NotSupportedException(SR.GetString(SR.net_unknown_prefix));

}

注意該方法中被我標註的幾行代碼。第行代碼定義了一個WebRequestPrefixElement對象Current,第行代碼則定義了一個ArrayList對象,並將一個已經存在的ArrayList對象PrefixList賦給它。第行代碼則通過Current對象的Creator字段來完成創建工作。整體來講,該方法就是在一個ArrayList中,根據參數Uri的值進行查找,如果找到,則創建相關的對象並返回,否則拋出異常。

現在我們需要明白兩個問題:

1、 WebRequestPrefixElement類的定義什麼?而該類型的Creator字段又屬於什麼類型?

2、 PrefixList對象存儲的內容是什麼?

我們首先看看WebRequestPrefixElement類的定義:

internal class WebRequestPrefixElement 

{

        public    string              Prefix;       

        public    IWebRequestCreate   Creator;

        public WebRequestPrefixElement(string P, IWebRequestCreate C)

{

            Prefix = P;

            Creator = C;

        }

}

很顯然,該類僅僅是提供一個將Uri前綴與IWebRequestCreate類型關聯的一個簡單對象而已。而Creator字段的類型正是IWebRequestCreate類型。UriIWebRequestCreate的關聯對象,可通過WebRequestPrefixElement的構造函數來傳入。

那麼,Current.Creator.Create(requestUri)創建對象的實質,就是通過調用IWebRequestCreate類型對象的工廠方法,來完成對WebRequest對象的創建。然而,究竟調用的是哪一個具體工廠類呢?也就是Current字段,其代表的IWebRequestCreate對象究竟是什麼?

根據第行代碼,Current的值是從prefixList列表中獲得的IWebRequestCreate對象。而prefixList值在該方法中就是PrefixList對象。而PrefixList其實是WebRequest類的一個私有屬性:

private static ArrayList PrefixList

{

get

{

    if (s_PrefixList == null)

{

         lock (typeof(WebRequest))

{

              if (s_PrefixList == null)

{

                  GlobalLog.Print("WebRequest::Initialize(): calling ConfigurationSettings.GetConfig()");

                  ArrayList prefixList = (ArrayList)ConfigurationSettings.GetConfig("system.net/webRequestModules");

 

                  if (prefixList == null)

{

                       GlobalLog.Print("WebRequest::Initialize(): creating default settings");

                       HttpRequestCreator Creator = new HttpRequestCreator();

 

                       // longest prefixes must be the first

                       prefixList = new ArrayList();                               

                       prefixList.Add(new WebRequestPrefixElement("https", Creator)); // [0]

                       prefixList.Add(new WebRequestPrefixElement("http", Creator));  // [1]

                       prefixList.Add(new WebRequestPrefixElement("file", new FileWebRequestCreator())); // [2]

                   }

 

                   s_PrefixList = prefixList;

               }

         }

    }

 

    return s_PrefixList;

}

set

{

     s_PrefixList = value;

}

}

PrefixList屬性的Get訪問器中,進行了一系列的判斷以及初始化工作,其中最重要的工作則是在其內部自動添加了三個元素,均爲WebRequestPrefixElement對象,而通過該對象,在prefixList中建立了UriIWebRequestCreate之間的關係:

prefixList.Add(new WebRequestPrefixElement("https", Creator)); // [0]

prefixList.Add(new WebRequestPrefixElement("http", Creator));  // [1]

prefixList.Add(new WebRequestPrefixElement("file", new FileWebRequestCreator())); // [2]

前兩個對象中,工廠類型均爲HttpWebRequestCreator,而第三個對象則爲FileWebRequestCreator。這正好是.Net提供的兩種繼承WebRequest的具體工廠類。

調用WebRequest的靜態方法Create()時,系統會根據傳入的Uri對象,在prefixList中搜索前綴與PrefixList列表中的uri匹配的WebRequestPrefixElement類型對象(通過String.Compare()方法)。如果找到,則根據一一映射的關係,去調用對應的工廠類,創建相應的Web請求實例,即Create()方法中的第行代碼。

再回過頭來看創建WebRequest對象的代碼:

WebRequest myRequest = WebRequest.Create(“http://www.cnblogs.com”);

根據前面分析的過程,這行代碼封裝的內部實現應該是如下步驟:

1、 將字符串”http://www.cnblogs.com”傳遞到WebRequest類的靜態私有方法Create()中;

2、 WebRequest的私有屬性PrefixList值賦給方法內的局部變量prefixList對象。此時會調用PrefixListGet訪問器。該訪問器會初始化PrefixList對象,將默認的UriIWebRequestCreate類型的值添加到PrefixList中;

3、 解析傳入的Uri,得到值”http”,對應的IWebRequestCreate對象爲HttpWebRequestCreator對象;

4、 調用HttpWebRequestCreator對象的Create()方法,創建HttpWebRequest對象,並返回。

執行步驟的時序圖如下:

factory6.GIF
    現在,考慮擴展的情況。如果此時派生於
WebRequest類的不僅僅有HttpWebRequestFileWebRequest子類,還有新增加的其他子類,例如FtpRequest類,它對應的Uri”ftp”。對應的工廠類爲FtpWebRequestCreator。此時應該如何創建?根據前面的分析來看,由於PrefixListGet訪問器中,並沒有添加其他的WebRequestPrefixElement對象,如果輸入Uri前綴”ftp”Create()方法是無法找到合適的IWebRequestCreate工廠對象的。難道,當我們擴展新的WebRequest子類時,還是需要修改WebRequest類中的Prefix屬性代碼嗎?如果真是這樣,前面引入的工廠類,以及許多設計就顯得拙劣可笑了。

顯然,這種可笑的錯誤,.Net Framework的設計師們是不可能犯下的。事實上,.NetWebRequest的創建過程中,引入了WebRequestPrefixElement類以及ArrayList對象PrefixList,就已經提示我們,Framework已經考慮到了擴展的可能。因爲,ArrayList對象是允許我們動態加入對象的。事實正是如此,在WebRequest類中,還提供了一個註冊WebRequestPrefixElement的公共靜態方法RegisterPrefix()

public static bool RegisterPrefix(string prefix, IWebRequestCreate creator)

{

bool Error = false;

int i;

WebRequestPrefixElement Current;

 

if (prefix == null)

{

    throw new ArgumentNullException("prefix");

}

if (creator == null)

{

    throw new ArgumentNullException("creator");

}          

 

lock(typeof(WebRequest))

{              

    ArrayList prefixList = (ArrayList) PrefixList.Clone();

    i = 0;              

    while (i < prefixList.Count)

{

         Current = (WebRequestPrefixElement)prefixList[i];

         if (prefix.Length > Current.Prefix.Length)

{

              // It is. Break out of the loop here.

              break;

         }

         if (prefix.Length == Current.Prefix.Length)

{

              // They're the same length.

              if (String.Compare(Current.Prefix, prefix, true, CultureInfo.InvariantCulture) == 0)

{

                    // ...and the strings are identical. This is an error.

                    Error = true;

                    break;

              }

        }

          i++;

    }

 

    if (!Error)

{

         prefixList.Insert(i, new WebRequestPrefixElement(prefix, creator));

         PrefixList = prefixList;

    }

}

return !Error;

}

只要在已有的PrefixList中沒有找到參數中傳遞進來的prefix值,就將新的prefix值和IWebRequestCreate對象插入到PrefixList中。通過該方法,就可以動態添加新的工廠類了。例如:

WebRequest.RegisterPrefix(“ftp”,new FtpWebRequestCreator());

WebRequest ftpRequest = WebRequest.Create(“ftp://www.cnblogs.com”);

通過這種實現方式,就可以解開具體工廠類、具體產品類與WebRequest靜態方法Create()之間的耦合。

.Net Framework中,關於WebRequest類對象的創建,其實現方式雖然看似複雜,但其本質仍然屬於Factory Method模式。但爲了類庫的更易於使用,並考慮到通用性和擴展性,又引入了類似於映射的WebRequestPrefixElement類以及ArrayList對象,同時又將具體工廠類對象設定爲internal,幷包裝到抽象產品基類WebRequest的靜態方法中。這種設計方法是我們在應用Factory Method模式設計自己的類庫時,值得借鑑的。

 

posted on 2005-08-15 11:41 wayfarer 閱讀(1461) 評論(7)  編輯 收藏 收藏至365Key 所屬分類: Design & Pattern
href="http://www.cnblogs.com/wayfarer/Services/Pingback.aspx" rel="pingback"/>
# re: .Net中的設計模式——Factory Method模式 2005-08-15 14:20 jeseeqing

  其實從第一個例子來說,個人覺得,對於已經預先定義的IShape,如果只是簡單地new一個IShape對象,不需要再爲每個IShape來創建一個IShapeFactory。因爲如果是在一個框架中,框架既然已經知道了有哪些預先定義的IShape,再爲每個IShape對象創建一個相應的IShapeFactory,就沒有什麼必要,當然如果創建IShape對象的邏輯比較複雜,則可以放在一個單獨的IShapeFactory。可以在框架內部定義一個內置的實現了IShapeFactoryclass,本classCreateShape方法來創建所有框架預先知道的IShape,當然其CreateShape方法需要帶上一個額外的參數來標識哪個IShape

UML可以爲




對於外部創建的
IShape對象,就可以採用文章中所說的通過註冊IShapeFactory來標識,其原理與文章中所說的是差不多的.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章