創建VS.Net可插拔組-將VS.Net的反射技術應用於簡單工廠模式,建立可插拔的軟件組件

出處:http://dev.csdn.net/author/leiyangcl/95c8c8af45ac408e9331c1dbebe1fb34.html

摘要
本文將從設計模式中的簡單工廠模式入手,並對簡單工廠將會出現的一些擴展性方式進行更爲詳細的描述,接着將應用VS.Net反射技術對簡單工廠提出擴充和優化,從而又介紹了在開發中能夠更加方開發過程的可插拔組件的構造。
 
本文中將以一個典型的三層架構中的數據訪問層作爲例子,對可插拔組件進行詳細的構造。相信讀者可以從中獲得將可插拔組件應用於其他功能性組件的開發中的啓示。
1.   簡單工廠
簡單工廠,相信每一個熟悉設計模式的軟件開發人員都不會陌生,它將使程序員在創建對象的過程中減少對具體類的依賴,擴充面向對象中多態性的意義。
 
如圖表1所示,當我們需要創建一個Product實例的時候就可以通過一個簡單工廠ProductFactory中的靜態函數GetInstance獲得,客戶端程序並不需要知道這個對象的具體類型,應用一個抽象的基類或者接口就可以實現各種需要的操作,例如在這個例子中調用Do函數,享受由多態性帶來的各種益處。

圖表 1
 
然而,如果我們對簡單工廠模式進行具體分析,其中工廠ProductFactoryGetInstance函數必然包括對參數type的解析,形成如代碼1所示的代碼:
 

static public Product GetInstance( string type )
{
     switch ( type )
     {
         case "ProductA" : return new ProductA();
         case "ProductB" : return new ProductB();
         default: throw new Exception("Un-support product type");
     }
}
代碼 1
 
某種程度上來說,在這樣的結構方式中,我們還是存在客戶端程序,ProductFactoryProduct三者之間的互相依賴,雖然簡單工廠已經對這種依賴進行了簡化,但是一些時候還是會引起維護上的麻煩。例如當我們需要增加一個新的Product派生類,例如ProductC,並且需要通過ProductFactory進行實例化這個派生類的情況下,我們需要做的事至少經過以下幾個步奏:
1.         打開ProductFactory所在的項目,
2.         找到ProductFactory中的GetInstance函數,
3.         在該函數中增加一個新的case語句case "ProductC" : return new ProductC();
4.         重新編譯發佈
 
如此長期以往,隨着這個派生類的增加和變化,如此這樣的擴展和修改也就將無休地進行。儘管在設計模式中又對簡單工廠進行了擴展,形成了工廠方法和抽象工廠等創建形模式[1],但是這些模式都逃不出對一個或大或小的簡單工廠的依賴。
2.   反射(Reflection)
反射技術[2]爲Microsoft在VS.Net Framework中所提供的一項可以在運行時處理對象和類型的技術,其中涵蓋了很多的對類型的操作,這裏將主要採用反射技術中獲得一個類型的實例的方法。從中我們可以找到一個以不變應萬變的簡單工廠模式的解決方案,避免對簡單的工廠的反覆修改。
 
應用這種反射方法,我們通過獲得描述一個類的類型,來獲得該類型的構造函數,調用該構造函數就可以獲得這個類的一個實例。
 
在如代碼2所示的代碼段中,我們用一個類的絕對路徑(包括命名空間和類名)和該類型所存在的動態鏈接庫(dll)名稱來描述一個類型,如”Demo.Product.ProductA, Product”,這個類型以string的形式存放在type變量中,反射就可以根據這個type信息取得所指定類型的實例。ConstructorInfo是我們通過類型實例獲得的類型構造函數,通過調用這個構造函數我們就可以直接獲得了所需要的實例。
 

Type reflectType = Type.GetType(type);
ConstructorInfo constructorInfo = reflectType.GetConstructor(Type.EmptyTypes);
return constructorInfo.Invoke(null) as Product;
代碼 2
 
雖然應用反射獲得的實例是一個object類型的實例,但是我們可以將其進行強制轉換,並應用於上面簡單工廠的例子中,將之前需要不斷增改的case語句用反射取而代之,形成如代碼3中所示的代碼中所實現的新型的簡單工廠。
 

public class ProductFactory
     {
         static public Product GetInstance( string type )
         {
              //獲得類型
              Type reflectType = Type.GetType(type);
              //獲得類型的構造函數
              ConstructorInfo constructorInfo = reflectType.GetConstructor(Type.EmptyTypes);
              //利用構造函數創建實例
              return constructorInfo.Invoke(null) as Product;
         }
     }
代碼 3
 
這個新型的簡單工廠的最大好處在於不管以後增加多少個Product派生類的定義,我們都能始終保持該工廠使用於運行時的狀態。不同的只是客戶端在調用該方法的時候需要改變參數type的定義方式,例如代碼4中的代碼段,固然這裏也需要知道一些具體實例化類型的信息,但是由string方式定義類型已經可以在很大程度上使得程序的應用由編譯時向運行時轉變。
 

ProductFactory.GetInstance("Products.ProductA, Products").Do();//調用ProductA
ProductFactory.GetInstance("Products.ProductB, Products").Do();//調用ProductB
ProductFactory.GetInstance("Products.ProductC, Products").Do();//調用ProductC
代碼 4
 
應用該套簡單工廠和反射的基本原理,我們可以對我們軟件中的一些需要應用簡單工廠實例化的類進行進一步的優化,創建可插拔的組件體系。
3.   可插拔組件
可插拔組件源於計算機的硬件組合。我們都知道,當我們需要裝配一臺電腦的時候,我們需要主板,CPU,顯示卡,聲卡,內存等各種組件。剛開始的時候,這些組件是電腦組裝商所提供的一塊集成電路板,但是隨着需要的增加或者事態的變化,更換組件的需求將會產生,例如升級顯示卡。
 
在我們面對這樣的需求,誰都不會希望在更換這一個零部件的時候同時需要更換其它部件,尤其是顯示卡的客戶端組件,主板。於是聰明的硬件提供商就想出了一個接口的方法,在主板上設立一個不變的插槽(接口),各種品牌的顯示卡只要滿足這個接口的要求,就可以將它插入插槽和主板一起工作。
 
軟件中的可插拔組件也類似的這樣的工作方式。這裏就一個大家都可能面對的問題,數據庫連接,闡述可插拔組件的創建,相信每一個有經驗的讀者都可以從中獲得構造其它可插拔的功能性組件的啓示。
3.1   例子-插拔數據訪問層
在數據庫項目開發中,我們會根據合理的軟件架構方式將應用程序分解爲幾個層次,形如圖表2所示。在VS.Net中這類結構可以方便進行構造,如硬件組成方法一樣,數據訪問層將履行對數據庫訪問的職責。

圖表 2
 
如果我們的軟件體系需要升級,例如需要從Access數據庫升級爲SQL Server。和升級硬件時一樣,也沒有人會希望在更換了數據庫類型的時候需要改變表現或者邏輯層。
 
面向對象的多態性無疑是一個合理的解決方案。如果邏輯層能夠通過統一的數據訪問層接口或者基類訪問數據庫,減小它們之間的耦合,當我們切換不同的數據庫的時候,需要處理的只是進行一個類似前面描述的簡單工廠應用中對抽象基類Product的實例化過程切換。
 
前面例子中的Product就等同現在所需要的數據訪問層抽象基類。邏輯層和簡單工廠的調用關係如圖表3所示的時序圖,邏輯層通過DataProviderFactory獲得抽象基類AbstractDataProvider實例,再通過這個實例進行數據庫操作,從而減少了對數據訪問層的直接依賴。如此這般,對一個應用程序的某一個組成部件的切換就形成了可插拔組件的構建需要。
 

圖表 3
 
參照前面所描述的Product架構方式,我們的DataProvider可以遵循如圖表4所示的繼承關係。其中,GetDataBySQLExecuteSQL將分別完成對有或者沒有返回數據集的SQL語句的執行(在本文中不對這個部分進行詳細的描述)。LoadConfig將負責對不同的配置信息進行解析,由於對於不同的數據庫會擁有不同的配置信息,例如Access數據庫需要指定對應的數據庫文件,SQL Server數據庫需要指定服務器名稱,在這裏將AbstractDataProvider中的LoadConfig聲明爲虛方法,在其派生類中重載這個方法解析不同的用XML表示的配置信息。
 

圖表 4
 
這樣的結構方式加上一個類似前面章節所描述的簡單工廠,我們可以簡化邏輯層對數據訪問層的直接依賴,能夠在需要對數據庫進行移植的時候保持其他兩個層次的不變。其具體代碼和結構大家都可以參照前面的簡單工廠方式得出。
3.2   IConfigurationSectionHandler
雖然在上面的結構中我們可以通過數據訪問層抽象結構,和應用反射的簡單工廠獲得實例,但是VS.Net中還可以應用更爲方便的切換組件的機制,應用配置文件就是其中一種。
 
IConfigurationSectionHandler爲Microsoft VS.Net Framework所提供的控制應用程序配置文件(web.config或者app.config)的諸多接口中的一個。其主要功能可以從一個應用程序的配置文件中獲得一個section的XML配置。
 
在代碼4中的web.config配置代碼分別用configSection節點中的DataProvider聲明和DataProvider節點描述了一個DataProvider配置信息,通過實現接口IConfigurationSectionHandler的可以很方便地對DataProvider節點進行需要的操作[4]。實現我們的集配置與反射爲一體的可插拔模型。
 

<?xmlversion="1.0"encoding="utf-8"?>
<configuration>
     <configSections>
         <sectionname="DataProvider"type="Demo.DataProvider.DataProviderFactory, Demo.DataProvider"/>
     </configSections>
     ……
     <DataProvidertype="Demo.DataProvider.SQLDataProvider, Demo.DataProvider"server="(local) "username="sa"password="sa"database="demo">
     </ DataProvider>
</configuration>
代碼 4
 
IConfigurationSectionHandler定義了一個Create"Demo.DataProvider.DataProviderFactory, Demo.DataProvider"形式,當完成了這些配置信息和類的創建後,應用程序就可以通過VS.Net提供的類ConfigurationSetting中的靜態函數GetConfig("SectionName")的激活這個實現類的Create,我們將DataProviderFactory,具體的UML靜態類圖如圖表5所示。作爲這個實現類方法,並且以參數形式傳入以SectionName命名的XmlNode配置信息。在這裏方法,形如代碼5所示。而且需要在配置文件申明中用type屬性的反射格式定義實現該接口的類的全路徑,如代碼4中所描述的

Object Create(object parent, object configContext, XmlNode section)
代碼 5
 
對於這樣的結構,當應用程序調用函數GetConfig(“DataProvider”),將激活我們DataProviderFactory中定義的函數Create。並將配置中的DataProvider節點用參數section傳入。

圖表 5
 
這裏我們應用的靜態函數GetInstance作爲調用者,形成如代碼6中完整的DataProviderFactory代碼。

     public class DataProviderFactory:System.Configuration.IConfigurationSectionHandler
     {
         public DataProviderFactory()
         {
         }
         static public AbstractDataProvider GetInstance(string type )
         {
              return ConfigurationSettings.GetConfig("DataProvider") as AbstractDataProvider;
         }
 
         #region IConfigurationSectionHandler 成員
         public object Create(object parent, object configContext, System.Xml.XmlNode section)
         {
              //通過反射獲得實例
              Type type = Type.GetType(section.Attributes["type"].InnerText);
              ConstructorInfo constructorInfo = type.GetConstructor(Type.EmptyTypes);
              AbstractDataProvider result=constructorInfo.Invoke(null) as AbstractDataProvider;
              //初始化配置
              result.LoadConfig( section);
              return result;
         }
         #endregion
     }
代碼 6
 
其中我們應用了存放在DataProvider節點中的type屬性(如代碼4中表示的Demo.DataProvider.SQLDataProvider, Demo.DataProvider)對所需要的具體類型進行了反射,並調用LoadConfig函數利用面向對象的多態性對不同的AbstractDataProvider派生類對象初始化了不同的基本信息。
 
以如此的結構方式,我們就完成了一個對可插拔的DataProvider組件的創建,這個可插拔方案解決了對於變化數據庫類型時邏輯層對數據訪問層和簡單工廠的依賴,同時當需要面對切換新的數據訪問層類型,就算是目前還未出現的類型的時,我們需要做的也就是實現一個繼承於AbstractDataProvider的派生類,再相應地修改相應的配置文件即可完成,完全的保全了工廠,邏輯層,表現層繼續運行於運行時的狀態。這樣的構建方式同樣也可以應用於更多的具有插拔需要的功能性組件。
4.   結束語
應用VS.Net提供反射機制實現的簡單工廠可以在很大限度上擴充簡單模式的擴展性,在更大程度上達到close modification, open extension的目的。配置文件的應用又將在更大程度上簡化簡單工廠的運作機制,實現可以插拔的軟件組件。然而,在軟件設計和VS.Net Framework應用的征途中我們還會有更多的領域需要去探索,使得我們的軟件產品更加具有可維護性和可擴展性。
參考文獻
1.          James W.Cooper C#設計模式;張志華,劉雲鵬譯;電子工業出版社;2003
2.          Reflection OverviewMicrosoft MSDN websitevisited on 28th June 2006http://msdn2.microsoft.com/en-us/library/f7ykdhsy.aspx;
3.          IConfigurationSectionHandler接口;Microsoft MSDN websitevisited on 29th June 2006http://msdn2.microsoft.com/zh-cn/library/system.configuration.iconfigurationsectionhandler.aspx
4.          VS.Net 配置文件概述;http://chs.gotdotnet.com/quickstart/aspplus/doc/configformat.aspx
 


 
發佈了2 篇原創文章 · 獲贊 0 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章