在.NET框架下的C#語言,和其他.Net語言一樣提供了很多強大的特性和機制.其中一些是全新的,而有些則是從以前的語言和平台上照搬過來的。然而,這種巧妙的結合產生了一些有趣的方法可以用來解決我們的問題。這篇文章將講述如何利用這些奇妙的特性,用插件(plug-ins)機制建立可擴展的解決方案。後面也將提供一個簡要的例子,你甚至可以用這個東西來替換那些已經在很多系統中廣泛使用的獨立的程序。在一個系統中,可能有很多程序經常需要進行數據處理。可能其中有一個程序用於處理雇員的信息,而另一個用來管理客戶關系。在大多數情況下,系統總是被設計為很多個獨立的程序,他們之間很少有交互,經常使用復制代碼的辦法來共享.而實際上這樣的情況可以把那些程序設計為插件,再用一個單一的程序來管理這些插件。這種設計可以讓我們更好的在不同的解決方案中共享公用的方法,提供統一的感觀。
圖片一是一個例子程序的截圖.用戶界面和其他常見的程序沒有什麼不同.整個窗體被垂直的分割為兩塊.左邊的窗格是個樹形菜單,用於顯示插件列表,在每個插件的分支下面,列出了這個插件所管理的數據.而右邊的窗格則用於編輯左邊被選中的插件的數據.各個插件提供各自的編輯數據的界面.圖片一展示了一個精巧的工作區.
開始
那麼,主程序必須能夠加載插件,然後和這些插件進行通信,這樣才能實現我們的設計.所有這些的實現可以有很多不同的方法,僅取決於開發者選擇的語言和平台.如果選擇的是C#和.Net,那麼反射(reflection)機制可以用來加載插件,並且其接口和抽象類可以用於和插件通信.
為了更好的理解主程序和插件之間的通信,可以先了解一下設計模式.設計模式最早由Erich Gamma提出,它利用架構和對象思想來實現通用的通信模型.不管組件是否具有不同的輸入和輸出,只要他們有相似的結構.設計模式可以幫助開發者利用廣受證明的面向對象理論來解決問題.事實上它就是描述解決方案的語言,而不用管問題的具體細節或者編程語言的細節.設計模式策略的關鍵點在於如何把整個解決方案根據功能來分解,這種分解是通過把主程序的不同功能分開執行而完成的.這樣主程序和子程序之間的通信可以通過設計良好的接口來完成.通過這種分解我們立即可以得到這兩個好處:第一,軟件項目被分成較小的不相干的單位,工作流程的設計可以更容易,而較小的代碼片斷意味著代碼更容易建立和維護.第二個好處在於改變程序行為的時候並不會關系到主程序的運行,主程序不用關心子程序如何,他們之間只要有通用的通訊機制就足夠了.
建立接口
在C#程序中,接口是用來定義一個類的功能的.接口定義了預期的方法,屬性,事件信息.為了使用接口,每個具體的函數必須嚴格按照接口的定義完成所描述的功能.列表一展示了上面例子程序的接口:IPlug.這個接口定義了四個方法:GetData,GetEditControl,Save和Print.這四個定義並沒有描述具體是怎麼完成的,但是他們保證了這個類支持IPlug接口,也就是保證支持這些方法的調用.
定制屬性
在查看代碼之前,討論總是先得轉移到屬性定制上面.屬性定制是.Net提供的一個非常棒的新特性之一,屬性對於所有的編程語言都是一種通用的結構.舉個例子,一個函數用於標識可訪問權限的public,private,或者protect標志就是這個函數的一個屬性.屬性定制之所以如此讓人興奮,那是因為編程人員將不再只能從語言本身提供的有限的屬性集中選擇.一個定制的屬性其實也是一個類,它從System.Attribute繼承,它的代碼被允許是自我描述的.屬性定制可以應用於絕大多數結構中,包括C#裡面的類,方法,事件,域和屬性等等.示例代碼片斷定義了兩個定制的屬性:PlugDisplayNameAttribute和PlugDescriptionAttribute,所有的插件內部的類必須支持這兩個屬性.列表二是用於定義PlugDisplayNameAttribute的類.這個屬性用於顯示插件節點的內容.在程序運行的時候,主程序將可以利用反射(reflection)來取得屬性值.
插件(Plug-Ins)
上面的示例程序包括了兩個插件的執行.這些插件在EmployeePlug.cs和CustomerPlug.cs中定義.列表三展示了EmployeePlug類的部分定義.下面是一些關鍵點.
1.這個類實現了IPlug接口.由於主程序根本不會知道插件內部的類是如何定義的,這非常重要,主程序需要使用IPlug接口和各個插件通信.這種設計利用了面向對象概念裡面的"多態性".多態性允許運行時,可以通過指向基類的引用,來調用實現派生類中的方法.
2.這個類被兩個屬性標識,這樣主程序可以判斷這個插件是不是有效的.在C#中,要給一個類標識一個屬性,你得在類的定義之前聲明屬性,內容附在括號內.
3.簡明起見,例子只是使用了直接寫入代碼的數據.而如果這個插件是個正式的產品,那麼數據總是應該放在數據庫中或者文件中,各自所有的數據都應該僅僅由插件本身來管理.EmployeePlug類的數據在這裡用EmployeeData對象來存儲,那也是一個類型並且實現了IPlugData接口.IPlugData接口在IPlugData.cs中定義,它提供了最基礎的數據交換功能,用於主程序和插件之間的通訊.所有支持IPlugData接口的對象在下層數據變化的時候將提供一個通知.這個通知實際上就是DataChanged事件的發生.
4.當主程序需要顯示某個插件所含數據列表的時候,它會調用GetData方法.這個方法返回IPlugData對象的一個數組.這樣主程序就可以對數組中的每個對象使用ToString方法得到數據以建立樹的各個節點.ToString方法是EmployeeData類的一個重載,用於顯示雇員的名字.
5.IPlug接口也定義了Save和Print方法.定義這兩個方法的目的在於當有需要打印或者保存數據的時候,要通知一個插件.EmployeePlug類就是用於實現打印和保存數據的功能的.在使用Save方法的時候,需要保存數據的位置將會在方法調用的時候提供.這裡假設主程序會向用戶查詢路徑等信息.路徑信息的查詢是主程序提供給各個插件的服務.對於Print方法,主程序將把選項和內容傳遞到System.Drawing.Printing.PrintDocument類的實例.這兩種情況下,和用戶的交互操作都是一致的由主程序提供的.