Live a simple life

          沉默(zhu_xing@live.cn)
          隨筆 - 48, 文章 - 0, 評論 - 132, 引用 - 0
          數據加載中……

          【Eclipse插件開發】Eclipse中類型擴展機制分析

           

                                  Eclipse中類型擴展機制分析

                                                                                                            朱興(zhu_xing@live.cn

          概要

                 在本篇文章中,將討論如下關鍵內容:

          1、 標準的適配器模式,包括類適配器模式和對象適配器模式,及其各自的優缺點

          2、 Eclipse runtime中的類型擴展機制,包括擴展類型的自動回調機制、已知擴展類型支持和未知擴展類型支持的討論

          3、 插件開發過程中,合理的使用Eclipse runtime中的類型擴展機制需要注意的地方

          說明:假設讀者大致了解適配器模式,對設計模式的使用有一點經驗

          標準適配器模式

                 適配器模式應該是我們日常所使用最多的結構型模式之一,“適配器模式把一個類的接口變換為客戶端所期望的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作”。

                 適配器模式的實現有兩種方式,借用《Java與模式》一書中的描述,將其稱之為類適配器模式和對象適配器模式,這兩種適配器模式也為我們很好地演繹了繼承和組合這兩種主要的代碼復用方式。與此同時,繼承和組合這兩種代碼的復用方式有其各自的適用場景(還是那句話,需求決定我們該怎么干 _~),而兩種適配器模式不恰當使用本質上也是和兩種代碼復用方式的不恰當使用緊密相關的。

                 為了便于下面的討論,我們延用設計模式書籍中使用的三個角色:

                 Adaptee(源,待適配):需要適配的類型,行為需要被復用

                 Target(目標):適配的方向,也就是我們期待的接口,客戶端定義的契約

                 Adapter(適配器):承擔類型匹配任務的具體類,Target類型實現類

                 說明:Adapter是需要實例化使用的,所以是具體類,而Target代表目標類型的抽象

                 下面,我們就簡要看一下類適配器模式和對象適配器模式。具體細節,可以參加《Java與模式》一書(當然,GOF的《設計模式》也可以 _~)

          類適配器模式

                  

                 我們的適配器Adapter同時繼承了目標類型Target(當然,這是必須的)和源Adaptee

                 缺點:

          1.如果源Adaptee有一系列類型(有共同的頂級父類型),那我們需要對這一系列類型

          中的每個類型(源及其子類型)都產生一個適配器類實現,也就是說,會引入大量的類型,帶來類型膨脹的問題。

          2.如果Target是一個抽象類,那么這樣做在不支持多繼承的編程語言(例如javac#)中是行不通的。(注意,既有代碼很有可能是以抽象類來扮演Target類型體系的頂級類型,畢竟很多開發者并不能非常清晰的來判斷如何區別使用接口和抽象類)。

          優點:

          由于是繼承了源,所以可以定制源類型的默認行為。注意,第一只能定制源類型體系中的某一特定類型,第二,這帶來了實現細節的耦合,而且定制有可能帶來用戶的疑惑,因為特定源類型的默認行為被修改了。

          對象適配器模式

          我們的適配器Adapter繼承了目標類型Target(當然,這是必須的),組合了源Adaptee

          缺點:

          無法定制Adaptee的行為。(其實也可以定制,需要引入另外的一個特殊的中間類型)

          優點:

          1. 由于采用了組合的方式,所以可以一次性的將引用的源類型及其子類型都適配到目標接口。這避免了從一種類型體系適配到另一種類型體系的過程中,產生大量的類型,避免了類型膨脹的問題(這也是Eclipse想極力避免的)。

          2. 最根本的優勢是,適合我們開發過程所面對的絕大多數適配需求。因為在設計良好的OO系統中,適配需求往往是將一個類型體系適配到另外一個類型適配體系,而每個類型體系往往都遵循了開閉原則,只暴露了抽象部分給客戶端(橋模式能夠很好的分離抽象和實現并使其獨立變化)。

          示例演示和進一步討論

          下面我們接著討論一下,類型體系適配需求示意如下:

          (說明:上圖中的紅色部分,就是我們所說的內部實現,不希望對用戶可見)

                        【客戶端模擬需求】

                          

           1 publicclass Customer {
           2 
           3            /**
           4 
           5             *模擬使用場景
           6 
           7             *
           8 
           9             *@paraminstanceInterface2類型的實例
          10 
          11             */
          12 
          13            publicstaticvoid run(Interface2 instance) {
          14 
          15               instance.operation();
          16 
          17            }
          18 
          19 }
          20 

           

          Interface2類型對象適配器】

           1 publicclass Interface2Adapter implements Interface2{
           2 
           3            private Interface1 interface1Instance;
           4 
           5     
           6 
           7            public Interface2Adapter(Interface1 instance) {
           8 
           9               this.interface1Instance = instance;
          10 
          11            }
          12 
          13     
          14 
          15            /* (non-Javadoc)
          16 
          17             * @see Interface2#operation()
          18 
          19            */
          20 
          21            publicvoid operation() {
          22 
          23               this.interface1Instance.operation();
          24 
          25            }
          26 
          27 }
          28 
          29 


          適配器使用

              

           1 public class Test {
           2 
           3     public static void main(String[] args) {
           4        Interface2 instance1 = new Interface2Adapter(new Interface1Impl());
           5        Customer.run(instance1);
           6 
           7 
           8        Interface2 instance2 = new Interface2Adapter(new Interface1Imp2());
           9        Customer.run(instance2);
          10     }
          11 }
          12 

          我們可以看到,我們的Interface2Adapter構造函數接收的是一個Interface1抽象類型,理論上我們可以用這一個Adapter類完成Interface1類型體系到Interface2類型體系的適配。

          疑問和進一步討論?

          1、我們的適配能夠被自動識別并使用嗎?

          分析一下上面的實例演示我們發現,在Java語言中并沒有相應的機制來判斷一個類型是否是可以適配的:上面的客戶端Customer需要的目標類型是Interface2類型,如果直接給它一個Interface1類型,Customer并不能判斷出來Interface1是否本身是可以被適配到其他類型。

          既然Java語言機制中沒有默認提供一種判斷特定類型是否可以適配到其他類型的機制,那么考慮到用戶的使用,一些類型在作為API暴露的同時,也需要將其對應的適配器作為API的一部分進行暴露。例如,上面例子中的Interface1作為API暴露,如果不暴露對應的適配器Interface2Adapter(作為內部實現隱藏)那么Interface1的用戶在面對Customer(接受的目標類型是Interface2)需求的時候遇到麻煩,他們不知道Interface1可以適配到Interface2,他們就可能需要自行開發對應的適配器。所以,我們開發一般的Java應用的時候,也確實是這么做的,將需要暴露的類型和默認提供的一些適配器也一起暴露。但是如果進一步想一下,如果類型的適配需求特別廣泛(注明:很多場景可能需要特定類型提供額外的服務,而這些服務并不能算是這種類型需要提供的核心服務),那么可能就需要提供并暴露大量的適配器類型給用戶(這當然是有點不優雅的)。

          有什么更好的辦法嗎?聯想一下,在Java語言中提供了一些類似的解決辦法可以供參考,例如java.long.Cloneable接口就聲明了一種類型可以被克隆的等等。那么如果我們也定義類似的接口,就叫做IAdaptable,來聲明一種類型可以被適配,再在接口中提供一個操作getAdapter來獲取對應的適配器實例,這樣的話我們就可以將我們的適配器類型進行隱藏。這樣,只需要將類型的核心服務暴露給用戶,如果用戶想請求額外的服務,可以調用getAdapter來獲取對應的目標類型(當然,我們在類型的API文檔中告訴用戶我們的類型可以默認被是適配為那些類型)。按照這個思路我們的代碼修改如下:

                               //聲明類型具有可以被適配為其他類型的能力

          publicinterface IAdaptable {

                         public Object getAdapter(Class adapter);

          }

          //修改我們的Interface1類型接口定義,聲明其可以被適配為別的類型

          publicinterface Interface1 extends IAdaptable{

                         publicvoid operation();

          }

          //修改我們的Interface1的類型實現,實現IAdaptable.getAdapter邏輯
              

           1 publicclass Interface1Impl implements Interface1{
           2 
           3      public void operation() {
           4           //todo:
           5      }
           6 
           7      public Object getAdapter(Class adapter) {
           8          //實現適配邏輯,適配到Interface2類型
           9           if (Interface2.class == adapter)
          10             return new Interface2Adapter(this);
          11             return null;
          12      }
          13 }
          14 
          15 

           

                 //修改我們的客戶端代碼
                      

           1 public class Customer {
           2      public static void run(Object source) {
           3         //首先檢查本身是否為Interface2類型
           4          if (source instanceof Interface2)
           5            ((Interface2) source).operation();
           6 
           7         //檢查類型是否可以被適配,如果可以,嘗試往Interface2類型適配
           8           else if (source instanceof IAdaptable) {
           9             IAdaptable adaptable = ((IAdaptable)source);
          10             Object instance = adaptable.getAdapter(Interface2.class);
          11             if (instance != null)
          12                ((Interface2) instance).operation();
          13         }
          14     }
          15 }
          16 


          說明:現在我們的客戶端接受的是一個弱類型了(其實應該是半弱類型,有相應的類型檢查)

          2、在能夠被自動識別和使用的基礎上,能否允許用戶參與提供適配的過程?

          //接著修改我們的Interface1Impl實現

           

           1 public class Interface1Impl implements Interface1{
           2      public void operation() {
           3          //todo:
           4      }
           5 
           6      public Object getAdapter(Class adapter) {
           7          //自定義適配邏輯(已知擴展),適配到Interface2類型
           8           if (Interface2.class == adapter)
           9             return new Interface2Adapter(this);
          10 
          11          //TODO: 調用別人貢獻的適配邏輯
          12           return AdapterManager.getAdapter(this, adapter)
          13      }
          14 }
          15 

           

          上面的代碼僅僅是示意性的代碼,AdapterManager類型也是暫時杜撰出來的。我們可以假設目前AdapterManager的作用是管理別人貢獻的適配邏輯,用只需要將對應的適配邏輯注冊到AdapterManager中,就可以參與到Interface1Impl的適配邏輯中,太完美了~_~。

          Eclipse中的類型擴展機制

                 Eclipse平臺本身是一個微內核(micro kernel)加核心插件(core plug-ins)的結構,微內核是指EcllipseOSGI實現Equinox(當然包含了擴展點機制的支持),這里的核心插件就是指:runtimeresourceworkbench。而這里的平臺運行時為我們提供的主要的特性是:類型擴展支持(IAdaptableIAdapterFactoryIAdapterManager)和線程支持(JobISchedulingRule)。我們今天要討論的就是類型擴展支持。

          Eclipse中的類型擴展需求

                 Eclipse中,一個特性類型經常會收到這樣的請求:請提供額外的服務。例如,用戶在一個視圖(提供了workbench seleciton service服務,)中選中了一個元素,這時候視圖會發送選擇事件出去,告訴其他視圖用戶選中了一個元素,屬性視圖(Properties View)接受到了這個選擇事件,就會向選中的對象發送請求:請提供能夠在屬性視圖顯示的服務。請注意,這種場景在Eclipse太普遍不過了,根源于Eclipse定位為一個可擴展的平臺,便于擴展和集成是Eclipse開發者的最大需求之一!!!

                 我們接著分析一下這種額外的服務。假設目前我們在定義一種類型,我們可能會將類型提供的服務劃分為兩類,以org.eclipse.core.resources.IResource為例:

                 核心服務:提供對底層資源的句柄代理(提供movedeletecopygetFullPath等操作),直接以API的方式暴露給用戶。

                 額外服務:需要類型擴展。進一步將額外服務劃分為兩類:已知額外服務(類型定義時提供)和未知額外服務(類型發布之后,用戶參與貢獻的額外服務)。對應的擴展類型我們將其稱之為已知擴展類型和未知擴展類型。對IResource類型來說,由于資源管理是Eclipse底層的核心模塊,在這個層面上面,它不可能為上層的功能模塊提供類型擴展,因為這違反了模塊分層劃分的原則。但是,IResource作為一個底層類型,肯定是要求可以擴展的,所以Eclipse針對IResource類型采用了邀請外部擴展(允許外部提供未知類型的適配器)的方式,詳細信息會在下面分析。

          Eclipse中的類型擴展機制

                 Eclipse類型擴展機制的核心本質】

                 Eclipse中的類型擴展機制,核心內容就是補充了Java語言的缺陷提供了自定義的類型擴展機制,基本上就是回答了上面的兩個問題:

                 1我們的適配能夠被自動識別并使用嗎?

                        Eclipse提供了org.eclipse.core.runtime.IAdaptable,用來聲明特定類型是否是可以

                 被適配的(adaptable)。并提供了默認適配器類org.eclipse.core.runtime.PlatformObject

                 Eclipse中,只要是繼承自以上接口或者抽象類的類型,就被視為“可擴展類型”。

              2在能夠被自動識別和使用的基礎上,能否允許用戶參與提供適配的過程?

                        Eclipse為我們提供了IAdapterFactoryorg.eclipse.core.runtime.IAdapterFactory

                 IAdapterManagerorg.eclipse.core.runtime.IAdapterManager)。用可以將自定義的

                 適配邏輯放入IadapterFactory中,然后注冊到IadapterManager中,注冊方式如下:

          1、 通過代碼靜態注冊(一般選擇在插件啟動時)

          IAdapterFactory.registerAdapters(IAdapterFactory factory, Class adaptable);

          2、 通過擴展點方式動態掛入

          org.eclipse.core.runtime.adapters擴展點(詳細信息參加Eclipse help

                 【已知類型擴展和未知類型擴展】

                 再接下來的討論之前,我們先來區分兩個概念(這兩個概念是俺大致起的,湊合著看吧~_~)。我們修改一下Interface1Impl實現如下:

                

          1 public abstract class PlatformObject implements IAdaptable {
          2     public Object getAdapter(Class adapter) {
          3         return AdapterManager.getDefault().getAdapter(this, adapter);
          4     }
          5 
          6 }
          7 

           

                

           1 public class Interface1Impl extends PlatformObject{
           2     public void operation() {
           3         //todo:
           4     }
           5 
           6       /* 
           7        * 已知擴展類型:Interface2
           8        * 未知擴展類型:遵循Eclipse”邀請法則“,兼容未知擴展類型
           9         * @see PlatformObject#getAdapter(java.lang.Class)
          10        */
          11 
          12      public Object getAdapter(Class adapter) {
          13          //已知擴展類型,適配到Interface2類型
          14            if (Interface2.class == adapter)
          15                return new Interface2Adapter(this);
          16 
          17          // 兼容未知擴展類型,通過PlatformObject.getAdapter查詢
          18            return super.getAdapter(adapter);
          19     }
          20 }
          21 
          22 


              已知擴展類型:類型定義者默認提供了對應的適配器實現,例如Interface2適配的Interface2Adapter未知擴展類型:以IAdapterFactory方式提供的類型統稱為未知擴展類型,通過IAdapterManager#getAdapter方法查詢使用。可能有人會問,我在定義一個類型的時候,就把類型適配邏輯放入到一個adapter factory中注冊到了IAdapterManager這應該算是已知擴展類型啊?但是對于Eclipse來說,其默認會遵守公平法則,即一個IAdapterFactory無論是那個開發者提供的,它并不關心,都是平等的。

          使用Eclipse中的類型擴展機制注意點

          【已知類型擴展和未知類型擴展】

                 已知擴展類型對于我們來說是很有把握的,既考慮到了靈活性又兼顧到了穩定性。而對于未知擴展類型來說,雖然有很大的靈活性,但是也帶來了不穩定性,畢竟這個適配類型不是自己提供的。這邊就需要有兩個事情需要考慮:

          1、 是否需要提供對未知擴展類型的支持

          Eclipse底層定義的很多類型都提供了對未知擴展類型的兼容,咱們自己定義的類型倒不一定。如果你能確定自定義的類型沒有類型擴展的需求,那么就不要繼承IAdaptable接口或者PlatformObject類。如果你能確定自定義的類型有非常明確的類型擴展需求,并且能確定需要適配到那幾種類型,那就只提供已知類型擴展。在確定自定義的類型有廣泛的類型擴展需求的情況下,再兼容未知擴展類型,這種類型一般來說是處于你應用中的底層模塊中。

          2、 明確已知擴展類型和未知擴展類型之間的優先級

          建議是將已知擴展類型設為高優先級,對IAdapterManager#getAdapter查詢得到的未知擴展類型設為低優先級別。注意,外部提供的未知擴展類型之際是不存在顯示的優先級別的,IAdapterManager#getAdapter會對注冊的IAdapterFacotry進行深度優先查找,查詢到第一個合適的就直接返回。

           

          【如何提供類型擴展】

          1、 不能破壞模塊分層原則,基礎模塊不應該為上層功能模塊的類型提供適配服務

          上層功能模塊是建立在基礎模塊的基礎之上的,如果讓基礎模塊為上層功能模塊中的類型提供適配服務,那肯定會破壞模塊分層的原則,也就沒有所謂的基礎模塊和上層功能模塊的概念了。

          如果基礎模塊中的類型需要適配到特定上層模塊中的類型,也應該是該上層模塊自己以IAdapterFactory的方式注冊,上層模塊自己創建對應的IAdapterFactoryAdapter實現。(當然,這就需要待適配的基礎類型本身兼容外部貢獻的未知擴展類型,PlatformObject的默認實現就是兼容外部貢獻的未知擴展類型的)。

          如果上層模塊中的類型需要適配到底層模塊中的類型,那就在上層模塊中提供適配邏輯,這是很自然的事情。Eclipse的開發者法則中就有一條:IResource適配法則,鼓勵開發者提供自定義類型到IResource系列類型的是適配邏輯。但是,同樣要避免一點:非UI模塊不應該為UI類型提供適配,否則會導致核心功能和UI的緊耦合。

          如果是平行層面的模塊呢?例如兩個上層功能模塊之間可能需要進行類型適配,這個問題就是仁者見仁、智者見智了。個人的建議是,不要輕易的做這種適配,應該首先考慮一下是否可以通過底層模塊作為橋梁,例如首先將模塊A中的類型適配為底層模塊中的類型,然后再將底層模塊中的類型適配為上層模塊B中的類型。如果還行不通,那就請反思一下現有模塊的設計劃分是否有問題,例如這兩個上層功能模塊是否劃分的過細。如果還行不通,那怎么做就

          看到過有些插件應用,里面也有幾十個插件工程。按照需求分析一下,肯定是有基礎模塊和上層功能模塊的概念,但是實際的代碼中去基本上反應不出來,可能對于很多人來說,管他基礎模塊還是上層模塊呢,代碼能運行就OK了。如果您現在的插件應用已經做了較好的底層模塊和上層功能規模的劃分,千萬不要因為誤用了Eclipse類型擴展機制而導致破壞了分層,那就非常得不償失了。~_

          2、 不能破壞Eclipse分層原則,非UI插件中定義的類型不應該提供UI類型適配服務

          一定要遵守,否則會直接破壞Eclipse倡導的分層原則,否則會造成核心功能和UI的緊密耦合,示意圖如下:

                  

               如上圖所示:

          1、      如果上層模塊A中的非UI類型需要適配到底層模塊core中的非UI類型,請在A.core插件中提供適配邏輯

          2、      如果上層模塊A中的非UI類型需要適配為底層模塊UI中的UI類型,請在A.UI插件中提供適配邏輯(避免非UIUI耦合)

          3、      如果底層模塊Core中的非UI類型需要適配到上層模塊A.UI中的UI類型,請在A.UI插件中提供適配邏輯

          4、      如果,請注意一般UI類型到UI類型的適配基本上情況很少

          Eclipse類型擴展機制的幾個缺陷】

          未知類型的沖突問題:前面我們分析過,對多個未知擴展類型之間不存在明顯的優先級別,IAdapterManager#getAdapter會對注冊的IAdapterFacotry進行深度優先查找,查詢到第一個合適的就直接返回。如果想獲取高的優先級,評估一下是否可以做為已知擴展類型提供,或者直接由定義該類型的插件直接提供IAdapterFactory注冊。關于優先級的問題,一般也沒有什么特別好的解決辦法。如果Eclipse提供了優先級的處理,那么可能既增加了用戶使用的復雜度,同時也并不能解決沖突的問題。

          IAdapterFactory的有效性問題IadapterManager提供了兩種getAdapterloadAdapter兩種接口如果以org.eclipse.core.runtime.adapters擴展點的方式掛入了IAdapterFactory實現getAdapter的方式不會去強制啟動你的插件,而loadAdapter的方式會去強制啟動你的插件如果是以代碼的方式注冊的,那也直接取決于所在插件是否啟動(你的IAdapterFactory注冊代碼是否被執行了)。這一點,很多時候會給人造成很大的疑惑

          【如何降低Eclipse類型擴展機制所帶來的編程復雜度】

                 Eclipse平臺運行時的類型擴展機制為開發者帶來了很大的靈活性,但是同時肯定也帶來編程復雜度,尤其是對于經驗很少的Eclipse插件開發人員。個人經驗,那就是如果你的類型實現了IAdaptable,請在API文檔中說明,提供內容如下:

          1、 提供了那些已知擴展類型。

          2、 是否邀請外部用戶貢獻適配邏輯。

          例如org.eclipse.core.resources.IResource接口的API文檔中有如下信息:

          Resources implement the <code>IAdaptable</code> interface;

          extensions are managed by the platform's adapter manager.

          總結

                 寫這篇文章的最大目的是希望解釋兩個事情:

                        Eclipse的類型擴展機制是怎么來的

                        使用Eclipse的類型擴展機制應該注意什么

             希望對大家有所幫助!



          本博客中的所有文章、隨筆除了標題中含有引用或者轉載字樣的,其他均為原創。轉載請注明出處,謝謝!

          posted on 2008-08-18 18:22 zhuxing 閱讀(3990) 評論(2)  編輯  收藏 所屬分類: Eclipse Plug-in & OSGI

          評論

          # re: 【Eclipse插件開發】Eclipse中類型擴展機制分析  回復  更多評論   

          寫的非常好,謝謝分享
          2009-10-13 18:03 | heng

          # re: 【Eclipse插件開發】Eclipse中類型擴展機制分析  回復  更多評論   

          深入淺出
          2009-11-26 10:07 | Icarus
          主站蜘蛛池模板: 额济纳旗| 青岛市| 阿巴嘎旗| 金阳县| 科尔| 博爱县| 遵化市| 汾阳市| 赤壁市| 开原市| 偃师市| 和田县| 孟村| 舟山市| 永安市| 衡南县| 天门市| 慈溪市| 东港市| 常州市| 天峻县| 聂荣县| 南江县| 南丰县| 屏东县| 沅江市| 永安市| 皋兰县| 罗甸县| 阿城市| 霍林郭勒市| 阿拉尔市| 封开县| 商洛市| 淮南市| 金塔县| 扎赉特旗| 太和县| 海阳市| 金华市| 昌邑市|