之前寫過不少關於外掛程式系統的文章,有介紹架構的,也有介紹外掛程式結構的。今天主要是分析一下外掛程式系統的組裝過程。
組裝包括兩個部分,介面的裝配、外掛程式互動關係的裝配。下面會介紹三種組裝策略,並簡單分析一下不同組裝策略的差異。
一、外掛程式系統的生命期
外掛程式是獨立的模組,每個模組向使用者提供一部分功能,只有將幾個模組組合起來才能真正給使用者帶來價值。
外掛程式系統分為裝載期和運行期兩個階段。
裝載期時,裝載程式將不同的外掛程式按照設定檔群組裝起來,主要包括介面布局和外掛程式介面之間的調用關係。
裝載完成後的外掛程式系統處於運行期。運行期時需要從兩個角度來分析:外掛程式內部行為,包括內部的函數調用以及使用者輸入的響應;外掛程式之間的互動。
在裝載期需要裝載器將系統必要的外掛程式按照需求組裝成可啟動並執行系統。下面介紹三種組裝過程,會採用“積木”的隱喻來解釋工作原理。
二、三種組裝策略2.1 Eclipse的最小化組裝
說起外掛程式不能不說到Eclipse了,它是一個非常成功的外掛程式系統,屬於微核心。這裡的核心也可以說是裝載器了。
首先說兩個概念,“外掛程式”和“外掛程式模板”。外掛程式是在系統運行期時的功能模組,從程式員的角度來看,是一些執行個體對象。外掛程式模板指的是設定檔,有外掛程式本身的配置資訊,比如說表徵圖、標題等,另外還有外掛程式之間的關聯關係(擴充和擴充點的概念)。可以通過指定的外掛程式模板產生外掛程式。
這裡認為Eclipse是最小化的組裝是因為在Eclipse中裝載過程是分散到系統中的各個角落。由父外掛程式裝載並建立子外掛程式,裝載並建立就是組裝過程。整個Eclipse在裝載期只會將設定檔讀入記憶體,產生外掛程式模板體系,隨後建立第一個外掛程式(主外掛程式)。至於其他的外掛程式都是主外掛程式的子外掛程式,或者孫子、重孫子輩的外掛程式。裝載器的功能非常簡單,原因在於它將組裝過程分散到系統中的各級父外掛程式的身上。
用積木的隱喻來解釋Eclipse的外掛程式組裝過程就是,由裝載器將第一個積木搭好,然後把設計圖紙放到大家都看得到的白板上面。如果有人想看某個積木之上的東西,那麼這個積木中內建的一個小的裝載機器會將積木之上的其他積木馬上放上來。
可以看到,Eclipse中的積木是非常智能的。因為它們本身就會搭積木。這樣系統的裝載器,要做的事情就非常少了。
2.2 裝載時綁定
裝載時需要確定外掛程式的介面呈現,外掛程式之間的互動。如果設計的好的話,外掛程式的外觀布局是可以和外掛程式的互動分開處理的。
外掛程式的外觀布局由設定檔指定,在裝載期,通過讀取系統的設定檔可以確定某一外掛程式放在什麼地方。在裝載期將所有的外掛程式介面全部放置到系統中,這樣系統的運行期在介面上和普通的系統沒有什麼區別——如果需要懶載入,可以用代理對象來實現,對裝載期沒有影響。
外掛程式之間的互動同樣通過設定檔指定。這時候需要用到介面才可隔離不同的外掛程式實現。介面是積木的外觀。裝載器可以將所有的外掛程式之間的介面綁定起來,可以使用觀察者模式來處理。在系統裝載的過程中,通過外部的指定,將所有觀察者的介面註冊到各個目標中,這樣目標和觀察者之間就可以相互連信了。
這個外掛程式系統就是一個圖狀的結構,外掛程式自身會儲存它和其他外掛程式的關係。裝載器的一個重要職責就是將這些關係建立起來,並且維護每個外掛程式內部的觀察者列表。
用這種方式組裝系統的話,在系統的裝載期由裝載器就已經將整個積木模型搭建好了。每個積木完成自己分內的事,處理好和自己吻合起來的積木之間的關係就可以。
2.3 運行時綁定
介面綁定和上面說的沒有什麼區別,但將外掛程式之間的關係,也就是每個外掛程式內部儲存的觀察者列表去掉了。這樣保證外掛程式本身是最清潔的。
外掛程式與其觀察者之間的匹配關係抽象成關係模式,這種對應關係在全域的關係列表中存著。
和第二種對比起來看,介面部分裝載沒有區別,但第二種組裝是將外掛程式互動關係在裝載時綁定到外掛程式內部,而這種是將外掛程式關係存成一張列表,每個外掛程式在需要調用的地方動態獲得觀察者的列表。實現採用的是以前介紹過的關係模式。
裝載器也是比較簡單的,介面部分不再討論,互動關係僅僅將設定檔中和互動相關的配置讀入記憶體就可以。
三、對比分析3.1 隱喻情境
裝載就是將現有的積木按照圖紙搭成模型的過程。
Eclipse的裝載器最會偷懶,他說,我幫你把第一個積木放在那邊了,至於其他的積木在用的時候讓積木自己裝去吧。還好這些積木都是比較智能的,如果積木比較弱智,不懂得裝載其他的積木,那麼它就只能放在最頂層,上面就不能放東西了。裝載器可不會管這些。
裝載時綁定的方式,裝載器就比較盡心儘力了,他會將所有的外掛程式按照圖紙搭好,包括積木的位置、外觀等,積木之間相互吻合的地方也是串連的一絲不苟。裝載完成後就是一個完整的系統了。當然,這時候的積木就不需要那麼智能,也不用管其他的積木怎麼擺放,只要把自己交給裝載器就行。
運行時綁定的方式中,裝載器既不像Eclipse中的那麼懶,也沒有第二種那麼勤快。它將積木的位置放好,外觀調好,但是積木之間的銜接處並沒有連起來。將每個積木和其他積木的吻合情況產生另外一張串連圖,在運行期,由積木自己根據這張圖找到吻合的積木。
3.2 延時載入
三種組裝策略中都可以實現延時載入的方式。這裡將延時載入單獨拿出來說是因為這個在外掛程式系統中太重要了。
Eclipse中的延時載入是天生的。裝載的策略決定了外掛程式本身就是延時載入的。但後兩種卻沒有這個能力,不過我們能夠使用代理外掛程式來實現同樣的效果。基本的思路就是,按照設定檔產生外掛程式的代理對象,裝載期將所有的代理對象裝配起來,在運行期由代理的特性自動裝載真實的外掛程式。對於裝載器和運行期來說,延時載入是透明的。
3.3 職責分配
第一種的裝載過程沒有和運行期明確分開,並且外掛程式也會負責裝載,所以說這裡的職責分配是不明確的,但帶來的一個好處就是微核心。其他兩種的裝載和運行期分的比較清楚,但核心比較大。
3.4 外掛程式互動
在Eclipse中外掛程式的組織是一個樹形結構,外掛程式之間的互動非常嚴格。當然,從系統耦合度的方面來看,這樣嚴格的限制是有好處的,但這種樹狀的組織圖並不能滿足所有的要求,尤其在行業軟體中。系統中的外掛程式數量多,互動非常複雜,是一種圖狀的結構的話,第一種模式就不適合了。第二種和第三種都能滿足圖狀互動的特點,互動的能力強。
如果採用後兩種方式來搭建外掛程式系統的話,一定要注意控制系統中的耦合度。
3.5 裝載期效率
Eclipse的裝載最簡單,效率是最高的。第二種裝載最複雜,效率也是最低的。第三種介於兩者之間。
3.6 運行期效率
Eclipse運行時需要即時裝配外掛程式,啟動並執行效率較低。第二種所有系統運行需要的因素都在裝載期處理好,系統運行時都是靜態調用,效能最好。第三種介於兩者之間。
如果外掛程式之間頻繁互動的話,第一種和第二種都是會在外掛程式內部儲存一個關聯的介面列表,所以效能上不會有問題。而第三種需要動態去取關聯的外掛程式,這裡需要注意。採用普通的List來儲存關係效能上不符合要求,可以考慮使用Hash表來最佳化效能。
四、後記
關於外掛程式系統寫了八篇文章,雖然有很多細節上沒有寫出來,但外掛程式系列也算是告一段落了。一方面出於對公司知識產權的保密,現在怎麼說都是在給公司做事,講得再細一些就要把代碼放上來了。另外今後我會多放一些精力在我的“讀易”系列上面,對於“讀易”這一塊大家褒貶不一吧,不過我個人覺得還是很有研究價值的。
今後還會零星地寫一些純技術的文章,也希望大家繼續關注.Net版塊。