標籤式用戶界面的故事續集

一年前的標籤式用戶界面的故事,試圖從一個GUI的問題引出一個關於插件體系的討論。一年後,發現自己有幸再次以插件模型爲基礎搭建了又一個軟件系統的靜態結構。這種模型已是如此的普遍,從網絡遊戲到領域應用框架,代碼的動態加載爲系統架構帶來的靈活性是如此的顯而易見,以至稍微上點規模的軟件都離不開它。

在代碼實現上,.Net提供了非常方便的方法。除了用前文提到的用靜態函數Assembly.Load()把程序集(Assembly)加載到當前默認的應用程序域(AppDomain)的方法以外,還可以在程序中動態創建一個新的AppDomain,然後把程序集加載到這個新的AppDomain中。後者的好處就是在你不需要這個程序集的時候,還也可以動態卸載掉它。這也是最近纔在資料上看到的,因爲以前似乎還沒有某種理由要求我們必須從一個進程中動態地卸載一段程序代碼,但我相信以後會用到的,比如你要自己寫代碼來更新或是微重啓一個7X24小時運行的服務器上的一些小模塊,而且設計的時候這些模塊的粒度已經小到無法用進程作爲邊界來劃分了。

這是我們的插件接口:

public interface ISomething
{
    
string DoSomething(string sourceAppDomain);
}

這是宿主的實現:

AppDomain ad = AppDomain.CreateDomain("MyNewAppDomain"nullnull);
ISomething obj 
= ad.CreateInstanceAndUnwrap("SomeAssembly","SomeAssembly.MyObject"as ISomething;
MessageBox.Show(obj.DoSomething(Thread.GetDomain().FriendlyName));
AppDomain.Unload(ad);

這是插件的實現,比如被編譯成SomeAssembly.dll。

namespace SomeAssembly
{
    
public class MyObject : MarshalByRefObject, ISomething
    
{
        
public string DoSomething(string sourceAppDomain)
        
{
            
return string.Format("call from {0} to {1}", sourceAppDomain, Thread.GetDomain().FriendlyName);
        }

    }

}

跟Remoting的機制一樣,所有穿過AppDomain的東西都要序列化。你可以在類的定義中加上Serializable這個Attribute,這個類實例在穿過AppDomain的邊界時就會被複制一份。如果換一種方式,你讓類派生於MarshalByRefObject,那麼它的實例就不會真正穿過AppDomain的邊界,而是在另外一端建立一個代理,因此在上面的例子中才會看到兩個地方調用Thread.GetDomain().FriendlyName返回的結果不一樣。這兩種方式在語法上看是正交的,但MarshalByRefObject其實也聲明成了Serializable,因此如果兩種方式同時存在的話,.Net會以後者爲先。

需要注意的是,雖然在兩個地方調用Thread.GetDomain().FriendlyName返回的結果不一樣,但兩個地方調用Thread.CurrentThread.ManagedThreadId的結果還是一樣的,也就是說不同AppDomain不同程序集中的代碼如今已運行在同一個線程中。如果運行的時候,另一個線程把插件卸載掉了,那麼當前運行的線程就會拋出異常。

---

衡量一個插件系統的好壞,接口的設計是個關鍵。它必須承載插件和宿主間的所有交互,但又不能太複雜,畢竟越複雜的東西越難以適應變化。這方面ASP.Net的設計似乎能給人不少啓發。比如它在某些接口中只定義了一個簡單的方法,這個方法的參數被定義成另外一個接口的類型,在這個被作爲參數傳遞的接口中提供衆多其他的方法和事件,來實現插件和宿主間的雙向通信。不知道有沒有人把這樣的方式也命名成某種設計模式,然後再用深奧的術語來詮釋它,讓它看起來是多麼的巧妙。然而,最終程序員使用這樣的接口來開發插件之前,還是必須從厚厚的文檔中找到關於接口中的這堆方法和事件調用順序的描述,就像WinForm開發人員必須知道Form Layout事件總是早於Form Load事件被觸發的一樣。不知道有沒有什麼辦法可以讓接口定義或者調用契約中包含這些時序邏輯。

---

最近好像形成了一種思維慣性,每分析一個系統的時候總是喜歡提取出其中的幾個關鍵要素來,然後擺弄這些要素的抽象定義,杜撰它們之間的八卦關係,於是可以裝作很高深的樣子,就像GoF那樣。下面總結一下插件體系,其實沒有什麼新意,所以也懶得畫圖了,如果要畫的話,應該跟軟件設計中司空見慣的夾心餅乾圖差不多。

宿主:提供公共服務
插件:實現業務邏輯
基礎:接口定義

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章