面臨的問題
在開發插件系統中,我們通常會面臨這樣的問題:
一些功能并不是在開啟時就要被使用的,例如VS中的大量功能對一個大部分程式員來說用不着,但架構本身卻應該向使用者提供該插件的相應資訊?
在可視化的插件功能清單中,我們不僅希望提供簡單的插件名稱資訊,更希望能以圖檔,或動畫等形式展示其功能特性,便于使用者選擇。
插入輔助類來解決上一個問題? 想法雖好,但破壞了“插件”的精髓,它應該是獨立可插拔的,如果存在其之外的輔助類,那真是得不償失。
據我所知,.NET的MEF插件系統提供了完整的插件系統架構,但可定制化程度不高。 一些插件功能是不需要每次都調用的,如果執行個體化所有的插件會帶來很大的資源開銷,而且不友善管理。是以本文将通過一些技巧,實作本文标題的目标:不執行個體化擷取插件資訊和可視化方法。我的項目本身是基于WPF的,但基本不影響整個文章的通用性。
如果想了解更多關于作者對本問題早期的思考,請看 我上一篇關于.NET插件系統的文章。
2. 改造attribute,提供類資訊的方法
attribute 即可以應用于程式設計元素(如類型、字段、方法和屬性 (Property))的描述性聲明。屬性與 .NET Framework 檔案的中繼資料一起儲存,并且可用于向公共語言運作時描述代碼或影響應用程式的運作時行為。
通過繼承atrribute,擴充我們想要獲得的使用方法,下面的代碼通過自定義attribute,提供其插件的名稱,描述,資源定義和類型。
///
/// 自定義的Attribute,可在架構中提供程式集名稱等資訊
///
public class XFrmWorkAttribute : Attribute // 必需以System.Attribute類為基類
{
public string DetailInfo //提供該類的詳細文字性說明
get;
set;
}
private string mainKind;
public string MainKind //該類類别
get { return mainKind; }
set { mainKind = value; }
private string name;
public string Name //提供類名稱
get { return name; }
set { name = value; }
private string myresource;
public string myResource //提供資源名稱
get { return myresource; }
set { myresource = value; }
// 值為null的string是危險的,是以必需在構造函數中指派
public XFrmWorkAttribute(string thisName, string thisKind, string thisDetailType)
// 定位參數
this.MainKind = thisKind;
this.Name = thisName;
this.DetailInfo = thisDetailType;
public XFrmWorkAttribute(string thisName, string thisKind, string thisDetailType, string thisResource)
this.myresource = thisResource;
自定義可由項目需求進行,具體請參考相關文檔,此處并不打算具體說明。本定義中,MainKind字段用于存儲該插件類型,這在一些搜尋方法中是必要的。 而myResource字段可儲存目前類的資源URI。這在WPF程式中尤為有效,在程式集中可嵌入圖形,音樂甚至視訊資源,可提供更好的使用者體驗。 而DetailInfo字段可儲存對該插件的一些文字性描述。
下面展示該attribute的使用方法
1 [XFrmWorkAttribute("Unity3D控制器", "IProgramWPF", "針對Unity3D的遊戲程式設計接口", "/XFrmWork.XMove.Program;component/Images/Unity3D控制器.jpg")]
2 public partial class Unity3DController : UserControl,IProgramWPF,IProgramUI
3 {
4 //類方法代碼
5 }
類attribute使用執行個體
其四個構造函數的參數分别是:名稱,類型(一般是實作的接口),類說明,資源名稱。
3. 主程式架構動态查找可用插件的方法
主程式架構如何獲知目前插件的定制資訊,并在需要的時候執行個體化呢? 我們可以設計一個類來存儲目前插件的資訊,即下面的ObjectBasicInfo。 myLOGOUrl可了解為上述的資源路徑,需要特别注意的是Type: 我們在此處存儲了該類的Type,使得能在需要的時候執行個體化之。
那麼,如何執行插件搜尋呢? 網上已經有大量的說明和介紹。 不外乎是搜尋某一程式集,擷取所有類型Type,并查詢其是否實作了某類接口。 但當工程中有不止一種插件類型時,我們就該考慮實作代碼複用:
可以定義一個特殊的類SingletonProvider,考慮到該類功能較為固定,采用單例模式實作。 在内部,給出了一個靜态字典和兩個靜态方法:
static Dictionary> myObjectBasicInfoDictionary: 插件字典:使用可通知集合ObservableCollection作為字典值,字典Key是接口名稱(再次聲明,此接口非一般意義上的接口,而是某種對插件分類的某種定義或限制,當然,可以使用C#的接口實作) 使用ObservableCollection僅僅是因為它在集合項目更改時可提供通知,友善WPF的資料綁定,如果不需要此項,你完全可以修改成你想要的集合類型如List
public static ObservableCollection GetInstance(string interfaceName) : 查詢被某種接口限制的所有插件方法:
具體資訊可以檢視具體代碼, 當字典中已經存在該接口插件集合,則直接傳回結果,否則執行查詢。需要注意:Assembly assembly = Assembly.GetCallingAssembly(); 可獲得目前被調用的程式集,這就保證了查找不同接口時是在儲存目前插件程式集的dll上動态執行的,這點非常重要,就可不用提供程式集名稱。 另外,當找到某一滿足需求的Type時,就可以查找目前type所有的attribute,并擷取資訊所有儲存至字典中。 在下次調用同樣接口的插件清單時,就可以不用再次執行查找。
public static Object GetObjectInstance(string interfaceName, int index) 封裝了的反射執行個體化方法。 類型參數是接口名稱和在插件清單中的位置,為了簡化, 類的構造函數必須是沒有形參的。
下面的代碼做了詳細的說明:
1 /// <summary>
2 /// 該類定義了插件系統通過attribute記錄的類基本資訊
3 /// </summary>
4 public class ObjectBasicInfo
5 {
6 public string myLogoURL { get; set; }
7 public string myName { get; set; }
8 public Type myType { get; set; }
9 public string myDetail { get; set; }
10 }
11 /// <summary>
12 /// 單例模式提供的插件搜尋器
13 /// </summary>
14 public class SingletonProvider
15 {
16 SingletonProvider()
17 {
18 }
19 static Dictionary<string, ObservableCollection<ObjectBasicInfo>> myObjectBasicInfoDictionary = new Dictionary<string, ObservableCollection<ObjectBasicInfo>>();
20
21 public static Object GetObjectInstance(string interfaceName, int index)
22 {
23 return Activator.CreateInstance(GetInstance(interfaceName)[index].myType);
24 }
25 public static ObservableCollection<ObjectBasicInfo> GetInstance(string interfaceName)
26 {
27 if (myObjectBasicInfoDictionary.ContainsKey(interfaceName)) //如果字典中存在該方法,則直接傳回結果
28 return myObjectBasicInfoDictionary[interfaceName];
29 else
30 {
31
32 ObservableCollection<ObjectBasicInfo> tc = new ObservableCollection<ObjectBasicInfo>(); //執行查找方法
33
34 Assembly assembly = Assembly.GetCallingAssembly();
35 Type[] types = assembly.GetTypes();
36 foreach (Type type in types)
37 {
38 if (type.GetInterface(interfaceName) != null && !type.IsAbstract)
39 {
40
41
42 // Iterate through all the Attributes for each method.
43 foreach (Attribute attr in
44 type.GetCustomAttributes(typeof(XFrmWorkAttribute), false))
45 {
46 XFrmWorkAttribute attr2 = attr as XFrmWorkAttribute;
47 tc.Add(new ObjectBasicInfo() { myLogoURL = attr2.myResource, myType = type, myDetail = attr2.DetailInfo, myName = attr2.Name });
48 }
49 }
50 }
51 myObjectBasicInfoDictionary.Add(interfaceName, tc);
52 return tc;
53 }
54 }
55
56
57 }
插件搜尋器
使用時非常友善,下面我們會介紹如何使用他。
4. 使用方法
下面我們以一個具體場景介紹這一方法的使用: 假設有一個通信系統,要求動态增減通信功能,如USB,序列槽,WIFI,藍牙等。 我們将其公共方法為接口ICommMethod ,所有通信方法都必須實作這一接口,并增加自定義的XFrmworkAttribute.
1 [XFrmWorkAttribute("标準藍牙通信", "ICommMethod", "提供支援絕大多數藍牙裝置的socket藍牙通信","/XFrmWork.XMove.Comm;component/Images/标準藍牙.jpg")]
2 public class BluetoothAdvanced : AbstartComm
4 public BluetoothAdvanced():base
5 ()
6 {
7
8 }
9 }
其他方法不一一列舉。
我們在一個插件管理類中直接這樣調用:
ComMethodList.DataContext = SingletonProvider.GetInstance("ICommMethod");
ComMethodList是我的系統中一個可用通信方法清單的WPF的Listbox控件。 直接将 ObservableCollection> 集合綁定到該控件的資料上下文中,即可顯示目前所有的通信清單。 Listbox中的對應index即插件集合的index,通過上面描述的執行個體化方法,就能反射執行個體化之。 讨論WPF的資料綁定超過了本文的範疇,但可查詢相關資料。讀者可以自行設計和使用該集合提供的資料。顯示效果如下圖:
5. 在已執行個體化的對象中擷取該類的attribute資料
還有一個遺留問題,即在執行個體化的對象中,我們依舊要獲得類的名稱,描述和其他相關資訊,如何做呢? 我們定義如下的attribute方法實作之
public class AttributeHelper
2 {
3 public static XFrmWorkAttribute GetCustomAttribute(Type source)
4 {
5
6 object[] attributes = source.GetCustomAttributes(typeof(XFrmWorkAttribute), false);
7 foreach (object attribute in attributes)
8 {
9 if (attribute is XFrmWorkAttribute)
10 return (XFrmWorkAttribute)attribute;
11 }
12
13 return new XFrmWorkAttribute("不存在定義","NULL","NULL","無定義資源");
14 }
15 }
若該類未實作自定義的attribute,傳回一個“空”值,提醒設計者或開發人員。
使用起來也很簡單,例如我們想獲得該對象的“名稱”:
public string PublicName
get { return AttributeHelper.GetCustomAttribute(this.GetType()).Name; }
注意,屬性通路器中,顯然隻應該實作get方法,它是運作時的不可修改對象。
6. 其他
本文的來源是作者項目中的實際需要,不執行個體化類而獲得類的相關資訊是一種很普遍的需求,attribute是一種做法,也可以通過xml實作,很多插件系統就是這麼做的,但對于輕量級的系統來說,attribute可能更合适,而單例模式的引用給該方法帶來了很大的友善。 有任何問題歡迎随時留言交流。
本文轉自 wws5201985 51CTO部落格,原文連結:http://blog.51cto.com/wws5201985/778509,如需轉載請自行聯系原作者