天天看點

.NET插件系統(三) 插件間通信問題——設計可自組織和注入的組裝程式

一.  問題的背景

       動态系統的要求之一,是不同子產品可以根據自身需求自動組裝,這往往通過配置檔案或使用者選擇進行。  這個基本問題在前面的文章中已經講述過了。

       但新的問題來了,我們定義了不同的插件a,b,c,那麼,不同插件之間的通信如何進行?

   如果系統本身的架構非常明晰而且不易更改,那麼面向固定接口的方法是最簡單友善的。 這也是大部分插件系統在“主結構”上使用的做法。

   但是,如果系統架構本身非常易變,連他們之間互動的接口都會随着問題的不同而不同。這就好像,系統中包含不同種類的插座和插頭,我們需要自動将符合要求的插座和插頭安裝好,實作自動組網。如何實作這種自組織的組裝程式呢?

二 . 具體的案例

      為了便于更好的說明問題,以一個我實際面對的設計問題進行分析,實在抱歉由于時間所限不能提供demo.

      我需要開發一個資料挖掘和處理平台,不同的功能通過插件的形式接入系統,并形成如左邊的可執行清單:

.NET插件系統(三) 插件間通信問題——設計可自組織和注入的組裝程式

      使用者可以很簡單的通過拖拽,将左邊的處理拖到後邊的算法執行清單中,當點選"運作"按鈕時,清單中的算法子產品會被順序或并行執行。

      這些算法是多樣的,比如資料挖掘中常用的  資料篩選,分詞,聚類,資料分類顯示等功能。你可以不必了解這些算法本身是什麼,但本文中,您可能需要了解他們之間的結果可能互相依賴。這些算法的共同特征,是必須依賴于前一個或多個算法的結果,同時本身還可以輸出給其他算法。 例如,資料分類顯示必須依賴于聚類的結果,聚類則必須依賴于前期分詞和資料篩選的功能。

      這種結構很像順序流動的資料流,或者像電網或自來水網的結構。那麼問題來了,系統執行前不知道這些算法到底是什麼,那怎麼能提供插件間互動的需求?  一種做法是,讀寫資料庫,隻要上一個算法告訴下一個算法資料的位置在哪裡就可以了,但這種做法很不“環保”,試想,好好的存在記憶體中的資料,幹嘛要寫到硬碟中再讀出來呢?這會造成無謂的開銷。

      另外,算法執行清單(右邊)的順序應該與組裝順序無關,意思是處在資料流上遊的子產品不一定就在執行清單的上遊。

    我們必須設計一套方法,能實作這些算法的互相通信。

 三 . 聲明可提供接口和注入接口需求

       首先,為了保證重用,算法子產品之間的通信方式隻能是接口或抽象類。不論如何,算法應該告訴管理器,它必須依賴什麼,它可以提供什麼。

       如果一個算法子產品可以提供某接口的結果,那麼它必須實作該接口。

   如果算法必須依賴某接口,那麼它應該最少包含一個該接口的内部成員,或者,也實作之(本文沒有考慮這種情況)。

       下面我們簡單實作兩個類:

        計算方法a可以輸出接口b和c,但計算方法b必須得到兩個接口b和c的結果。

    兩個方法方法非常簡單,繼承于abstractprocessmethod類,你不需要關心這個類的具體内容,隻需注意 test1實作了兩個接口ib和ic,這兩個接口都能提供兩個字元串。test2類則必須獲得ib和ic兩個接口的字元串成員。

       我們可以通過自定義attribute實作可提供和依賴的接口的辨別。 本系統中使用了兩個自定義的attribute:

[selfbuildclassattribute(new string[] { }, new string[] { "ib", "ic" })]

        (xfrmworkattribute是插件的标記,詳情可見我上一篇關于插件的文章)     

 這兩個類的作用已經在注釋上寫清楚了,您可以結合test1和test2兩個類的具體實作來了解:  test1不需要依賴任何接口,但可以輸出兩個接口ib,ic。  test2方法需要依賴ib和ic兩個接口,是以它有兩個成員變量,并加上了标記,标記的内容是該接口的名稱。

  下面,我們要做的工作,就是在運作時,自動将test1的方法注入到test2的内部接口上。

四 .  實作内部組裝

        當使用者點選運作時,系統會自動實作接口裝配,并按照執行政策執行清單當中的算法子產品。

   在我的系統中,所有算法的抽象接口都是iprocess,但在這篇文章中,自組織并不一定需要該接口。系統儲存的算法儲存在了icollection<iprocess>接口中。而具體裝配的方法,則定義在selfbuildmanager中。

     具體的方法請參考代碼的注釋部分。由于代碼注釋已經很詳細了,是以不做更多解釋。

五. 實作和驗證

     我們将計算方法a和計算方法b都拖入算法執行清單中:

.NET插件系統(三) 插件間通信問題——設計可自組織和注入的組裝程式

     并單擊執行按鈕:

.NET插件系統(三) 插件間通信問題——設計可自組織和注入的組裝程式

      可以看到,接口确實被正确指派了。設計成功。

六. 必須考慮的問題和擴充點

     雖然設計成功,但系統有一些不可避免的問題:

如果一個需求者發現有不止一個滿足該需求的提供者,那麼如何選擇?目前系統未作此區分,僅僅在找到第一個适配對象後停止搜尋。合适的方法是提供使用者介入的控制方案,即使用者可以用線将不同算法的需求和提供聯系起來,當然,該需求暫時有些複雜,如果作者實作了它,一定會公開其方法。

性能和靈活性: 通過反射實作的方法必須讨論性能,好在系統隻執行一次裝配過程,并盡可能的通過标記簡化搜尋條件。  但應該研究更好的搜尋方法。

該功能的易用性: 作者本人認為該系統是足夠易用的,你可以簡單地将需求和提供接口的字元串清單标記在類前,并将需求的接口标記在需求方的成員變量前,暫時沒有想到更好的做法。

互相依賴問題:一種可能的情況是算法a依賴算法b的結果,算法b依賴a的結果,這種情況一定是不允許的嗎?不一定,但若能處理這種需求,就可能實作更強的靈活性,同時帶來更複雜的組裝邏輯。

   有任何問題,歡迎讨論!

繼續閱讀