目 錄
第九章 插件引擎設(shè)計... 2
9.1 框架的契約-接口... 2
9.2 插件的雛形-抽象類... 3
9.3 實現(xiàn)接口... 4
9.4 反射機制... 5
9.5 反射工具類... 8
9.6 小結(jié)... 9
在介紹《第10章 宿主程序詳細(xì)設(shè)計》之前對接口和插件的相關(guān)內(nèi)容進(jìn)行一下整體介紹,在設(shè)計宿主程序的時候會用到這些知識,也是宿主程序與插件之間交互的核心內(nèi)容。
插件式框架的宿主程序啟動后,它首先會加載相應(yīng)的配置文件(例如:設(shè)備驅(qū)動配置文件等),找到相應(yīng)的插件程序集,這些程序集以DLL文件格式存在,框架的宿主程序會找到指定的插件類型,由插件引擎依據(jù)插件類型(例如:IRunDevice)生成對象實例,由框架的宿主程序的管理器對插件實例進(jìn)行管理和調(diào)度。
一個插件程序集可能包括多個插件類型,那么框架宿主程序是如何識別這些類型是否為要加載的插件呢?每個插件對象都有一個身份標(biāo)識-接口,這個標(biāo)識在框架設(shè)計中被稱為“通訊契約”。接口可以被看作是一種定義了必要的方法、屬性和事件的集合,因此宿主程序就可以通過這種契約來生成具體的實例對象,并對其他組件或接口公開可操作的對象。
插件式框架作為一個高聚合低耦合的平臺,它的功能定義與功能實現(xiàn)之間是分離的。只要符合插件規(guī)范的二次開發(fā)組件都可以掛載到框架平臺中,而它并不并心這些組件的具體功能。當(dāng)然,框架平臺提供了一些必要的信息、機制來保證這些組件能夠正常實現(xiàn)二次開發(fā)的功能。
在具有多個邏輯層次的結(jié)構(gòu)設(shè)計中,各層之間的通信大多通過接口來實現(xiàn),接口不會輕易改變,如果一個層的功能發(fā)生變化,不會影響其他層;只要正常實現(xiàn)了接口的組件功能,那么程序的運行就沒有問題。這種做法使得各層之間的相互影響降低到最低,總之,接口在多業(yè)務(wù)層級中能夠更好的解耦。
在大部分功能性的編程和設(shè)計工作中,很少需要考慮“接口(interface) ”的情況,如果我們僅僅滿足通過控件的方式在IDE上編程和使用.NET Framework中一般的類庫,可能永遠(yuǎn)不會在程序中使用到接口,即使在C#等面向?qū)ο笳Z言的語法書中讀者會無數(shù)次看到過這個詞,也只是完成一般性的功能,并未掌握面向?qū)ο缶幊痰暮诵乃枷搿?/p>
接口是一般行為的定義和契約。如貓和狗等動物,只需要將一般性的、公共性的屬性、動作等定義在接口里,例如:有眼睛、可以吃東西等。盡管不同動物之間存在很大差異,但是接口并不考慮它們各自的特性或功能的差異,例如:什么顏色的眼睛、吃什么東西等。它只關(guān)心這些類型都必須實現(xiàn)接口定義的所有功能,而實現(xiàn)了這個接口就可以被看作是一種動物。
因此,接口的兩個主要的作用是:
n 定義多個類型都需要的公共方法、屬性。
n 作為一種不可實例化的類型存在。
繼承接口實現(xiàn)定義的方法、屬性等,實際上是實現(xiàn)了一種策略。
接口與抽象類非常相似,例如兩者都不能new一個實例對象,卻都可以作
為一種契約和定義被使用。但是接口和抽象類有本質(zhì)的不同,這些不同包括:
n 接口沒有任何實現(xiàn)部分,但是抽象類可以繼承接口后部分實現(xiàn)代碼。
n 接口沒有字段,但是抽象類可以包含字段。
n 接口可以被結(jié)構(gòu)(Struct)繼承,但是抽象類不行。
n 抽象類有構(gòu)造函數(shù)和析構(gòu)函數(shù)。
n 接口僅能繼承自接口,而抽象類可以繼承自其他類和接口。
n 接口支持多繼承,抽象類僅支持單根繼承。
在MSDN的相關(guān)內(nèi)容中,給出了如下關(guān)于接口與抽象類的建議:
n 如果預(yù)計要創(chuàng)建組件的多個版本,則創(chuàng)建抽象類。抽象類提供簡單易行的方法來控制組件版本。通過更新基類,所有繼承類都隨更改自動更新。另一方面,接口一旦創(chuàng)建就不能更改,如果要更新接口的版本,必須創(chuàng)建一個全新的接口。
n 如果創(chuàng)建的功能將在大范圍的全異對象間使用,則使用接口。抽象類應(yīng)主要用于關(guān)系密切的對象,而接口最適合為不相關(guān)的類提供通用的功能。
n 如果要設(shè)計小而簡練的功能模塊,應(yīng)該使用接口。如果要設(shè)計大的功能單元,則應(yīng)該使用抽象類。
n 如果要在組件的所有實現(xiàn)間提供通用的已實現(xiàn)功能,應(yīng)該使用抽象類。抽象類允許部分實現(xiàn)類,而接口不包含任何成員的實現(xiàn)。
接口和抽象類都可以作為“通信契約”,為子類提供規(guī)范。下面定義一個接口和抽象類。
//定義一個接口public interface IMyInterface{ void Action(int type); string Method(int para);}//定義一個抽象類public abstract class BaseAbstract:IMyInterface{ public abstract void Action(int type); //繼承此類抽象類時必須實現(xiàn)這個方法。 public string Method(int para) //實現(xiàn)這個方法 { return para.ToString(); }}
繼承接口的話,需要實現(xiàn)全部定義的方法或?qū)傩裕缦麓a:
public class MyClass1:IMyInterface{ public void Action(int type) { Console.WriteLine(type.ToString()); } public string Method(int para) { return para.ToString(); }}
繼承抽象類的話,只需要實現(xiàn)抽象類沒有實現(xiàn)的方法或?qū)傩裕话銥槌橄蠓椒ɑ驅(qū)傩裕缦麓a:
public class MyClass2:BaseAbstract{ public void Action(int type) //繼承抽象類,只需要實現(xiàn)這個函數(shù)。 { Console.WriteLine(type.ToString()); }}
有了設(shè)備驅(qū)動或插件,還不能掛載到框架平臺的宿主程序中。我們考慮的問題是:已經(jīng)有了任意多個類型插件程序集,框架平臺如何從程序集中根據(jù)類型定義在內(nèi)存中生成插件對象?
回顧普通情況下程序引用其他程序集組件的過程。首先,需要使用“添加引用”對話框加載程序集。然后,通過using關(guān)鍵字引用命名空間。最后,在命令空間下找到相應(yīng)的類,并new出來一個實例。這是一種靜態(tài)加載程序集的方式。
在插件式應(yīng)用框架中,這種方法并不適合。宿主程序在編譯時并不知道它將要處理哪些程序集,更沒有辦法靜態(tài)的將插件類型通過using關(guān)鍵字引入,這些都是在運行時才能獲得的信息。在這樣的情況下,也無法使用靜態(tài)方法和new關(guān)鍵字來生成一個類型實例。而是需要在運行時獲得相關(guān)信息動態(tài)加載程序集,這個過程被稱為反射。
反射是動態(tài)發(fā)現(xiàn)類型信息的一種能力,它類似后期綁定,幫助開發(fā)人員在程序運行時利用程序集信息動態(tài)使用類型,這些信息在編譯時是未知的,反射還支持更高級的行為,如能在運行時動態(tài)創(chuàng)建新類型,并調(diào)用這些類型的方法等。
JIT編譯器在將IL代碼編譯成本地代碼時,會查看IL代碼中引用了那些類型。在運行時,JIT編譯器利用程序集的TypeRef和AssemblyRef元數(shù)據(jù)表的記錄項來確定哪一個程序集定義了引用的類型。在 AssemblyRef元數(shù)據(jù)記錄項中記錄了程序集強名稱的各個部分—包括名稱,版本,公鑰標(biāo)記和語言文化。這四個部分組成了一個字符串標(biāo)識。JIT編譯 器嘗試將與這個標(biāo)識匹配的程序集加載到當(dāng)前的AppDomain中。如果程序集是弱命名的,標(biāo)識中將只包含名稱。
.NET Framework中,為了實現(xiàn)動態(tài)加載,需要熟悉Assembly、Type和Activator等工具類的方法。框架平臺主要使用了Assembly工具類,這個類中包括Load、LoadFrom和LoadFile。
1. Assembly的Load方法
在內(nèi)部CLR使用Assembly的Load方法來加載這個程序集,這個方法與Win32的LoadLibray等價。在內(nèi)部,Load導(dǎo)致CLR對程序集應(yīng)用一個版本重定向策略。并在GAC中查找程序集,如果沒有找到,就去應(yīng)用程序的基目錄,私有路徑目錄和codebase指定的位置查找。如果是一個弱命名程序集,Load不會向程序集應(yīng)用重定向策略,也不會去GAC中查找程序集。如果找到將返回一個Assembly的引用,如果沒有找到則拋出FileNotFoundException異常。注意:Load方法如果已經(jīng)加載一個相同標(biāo)識的程序集只會簡單的返回這個程序集的引用,而不會去創(chuàng)建一個新的程序集。
大多數(shù)動態(tài)可擴展應(yīng)用程序中,Assembly的Load方法是程序集加載到AppDomain的首選方式。這種方式需要指定程序集的標(biāo)識字符串。對于弱命名程序集只用指定一個名字。
2.Assembly的LoadFrom方法
當(dāng)我們知道程序集的路徑的場合,可以使用LoadFrom方法,它允許傳入一個Path字符串,在內(nèi)部,LoadFrom首先調(diào)用AssemblyName的靜態(tài)方法GetAssemblyName。這個方法打開指定的文件,通過AssemblyRef元數(shù)據(jù)表提取程序集的標(biāo)識,然后關(guān)閉文件。隨后,LoadFrom在內(nèi)部調(diào)用Assembly的Load方法查找程序集。到這里,他的行為和Load方法是一致的。唯一不同的是,如果按Load的方式?jīng)]有找到程序集,LoadFrom會加載Path路徑指定的程序集。另外,Path可以是URL。
3.Assembly的LoadFile方法
這個方法初一看和LoadFrom方法很像。但LoadFile方法不會在內(nèi)部調(diào)用Assembly的Load方法。它只會加載指定Path的程序集,并且這個方法可以從任意路徑加載程序集,同一程序集如果在不同的路徑下,它允許被多次加載,等于多個同名的程序集加載到了AppDomain中,這一點和上面的兩個方法完全不一樣。但是,LoadFile并不會加載程序集的依賴項,也就是不會加載程序集引用的其他程序集,這會導(dǎo)致運行時找不到其他參照DLL的異常。要解決這個問題,需要向AppDomain的AssemblyResolve事件登記,在回調(diào)方法中顯示加載引用的程序集。類似于這樣:
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args){ if (args.Name != null) { return Assembly.LoadFrom(string.Format("{0}//plugin//{1}.dll", application.StartupPath, new AssemblyName(args.Name).Name)); } return null;}
特別注意:要測試LoadFile有沒有加載引用的DLL,切不可將DLL拷貝到應(yīng)用程序的根目錄下測試,因為該目錄是CLR加載程序集的默認(rèn)目錄,在這個目錄中如果存在引用的DLL,它會被加載,造成LoadFile會加載引用DLL的假象。可以在根目錄下新建一個子目錄如plugin,把引用的dll拷貝到這里面進(jìn)行測試。
反射機制也有它的缺點:安全性和性能方面。但是,框架平臺在啟動的時候、以及增加新設(shè)備驅(qū)動(插件)的時候需要使用反射,一旦加載到宿主程序中,與靜態(tài)引用程序集沒有本質(zhì)區(qū)別,都是寄存在內(nèi)存中。
插件式框架平臺使用反射掛載設(shè)備驅(qū)動,在宿主程序中運行,需要一個專用的工具類來完成相關(guān)功能。代碼定義如下:
/// <summary>/// 一個輕便的 IObjectBuilder 實現(xiàn)/// </summary>public class TypeCreator : IObjectBuilder{ public T BuildUp<T>() where T : new() { return Activator.CreateInstance<T>(); } public T BuildUp<T>(string typeName) { return (T)Activator.CreateInstance(Type.GetType(typeName)); } public T BuildUp<T>(object[] args) { object result = Activator.CreateInstance(typeof(T),args); return (T)result; } /// <summary> /// 框架平臺主要使用了這個函數(shù)。 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="assemblyname"></param> /// <param name="instancename"></param> /// <returns></returns> public T BuildUp<T>(string assemblyname, string instancename) { if (!System.IO.File.Exists(assemblyname)) { throw new FileNotFoundException(assemblyname + " 不存在"); } System.Reflection.Assembly assmble = System.Reflection.Assembly.LoadFrom (assemblyname); object tmpobj = assmble.CreateInstance(instancename); return (T)tmpobj; } public T BuildUp<T>(string typeName, object[] args) { object result = Activator.CreateInstance(Type.GetType(typeName), args); return (T)result; }}
下一章節(jié)介紹宿主程序詳細(xì)設(shè)計,需要對反射機制有一定的了解,并且會使用到上面的工具類,并在此基礎(chǔ)上進(jìn)行擴展。
框架平臺就要完全了,只需要一小步了。
作者:唯笑志在
Email:504547114@QQ.com
QQ:504547114
.NET開發(fā)技術(shù)聯(lián)盟:54256083
文檔下載:http://pan.baidu.com/s/1pJ7lZWf
官方網(wǎng)址:http://www.bmpj.net
|
新聞熱點
疑難解答