原文地址:http://www.eclipsezone.com/articles/what-is-iadaptable/
IAdaptable在Eclipse里是一個(gè)非常重要的接口。對(duì)于Eclipse開(kāi)發(fā)老手來(lái)說(shuō),它就像異常處理和抽象類一樣尋常;但是對(duì)新手而言,它卻令人感到困惑和畏懼。這篇文章將向你解釋IAdaptable到底是什么,以及它在Eclipse里起到的作用。
類型轉(zhuǎn)換
Java是所謂的強(qiáng)類型語(yǔ)言,也就是說(shuō),每個(gè)實(shí)例都對(duì)應(yīng)一個(gè)類型。其實(shí)類型分為兩種:聲明類型和運(yùn)行時(shí)類型(也分別被稱為靜態(tài)類型和動(dòng)態(tài)類型)。像Python這樣的弱類型語(yǔ)言常被稱為無(wú)類型的語(yǔ)言,其實(shí)嚴(yán)格說(shuō)來(lái)不是這樣,因?yàn)槊總€(gè)實(shí)例都對(duì)應(yīng)一個(gè)運(yùn)行時(shí)類型,只是你并不需要聲明這一點(diǎn)而已。
現(xiàn)在回到Java,為了能夠執(zhí)行一個(gè)類的某個(gè)方法,這個(gè)方法必須在聲明類型中可見(jiàn),換句話說(shuō),即使在運(yùn)行時(shí)實(shí)例是某個(gè)子類型,你也只能執(zhí)行那些父類型里定義的方法。
List list = new ArrayList();
list.add("data"); // 正確,add是List里定義的方法
list.ensureCapacity(4); // 不正確,ensureCapacity()只在ArrayList被定義
如果一定要執(zhí)行特定類型的方法,我們必須先強(qiáng)制轉(zhuǎn)換這個(gè)實(shí)例到正確的類型。對(duì)于上面的例子,我們可以將list轉(zhuǎn)換為ArrayList(譯注:原文In this case, we can cast ArrayList to List,懷疑是筆誤),因?yàn)锳rrayList實(shí)現(xiàn)了List接口,你甚至可以在運(yùn)行時(shí)通過(guò)instanceof關(guān)鍵字檢驗(yàn)list是否為ArrayList的一個(gè)實(shí)例。
可擴(kuò)展的接口
不幸的是,一個(gè)類可能并沒(méi)有實(shí)現(xiàn)你需要的接口,這樣就無(wú)法進(jìn)行強(qiáng)制類型轉(zhuǎn)換了。原因有很多,比如只在少數(shù)情況下才需要這個(gè)接口,或者你需要的接口是在另一個(gè)不相關(guān)的庫(kù)里,又或者接口是有了類以后才開(kāi)發(fā)出來(lái)的,等等。
這時(shí)你就需要IAdaptable了。可以把IAdaptable想象為一個(gè)能夠動(dòng)態(tài)進(jìn)行類型轉(zhuǎn)換的途徑。對(duì)比下面的直接類型轉(zhuǎn)換:
Object o = new ArrayList();
List list = (List)o;
換一種方式,我們可以這樣做:
IAdaptable adaptable = new ArrayList();//譯注:這里的ArrayList應(yīng)該不是指java.util.ArrayList
List list = (List)adaptable.getAdapter(java.util.List.class);
這就是上面所說(shuō)的動(dòng)態(tài)類型轉(zhuǎn)換,我們所做的事情是試圖把a(bǔ)daptable轉(zhuǎn)換為一個(gè)List實(shí)例。
那么,當(dāng)可以直接轉(zhuǎn)換的時(shí)候?yàn)槭裁匆M(fèi)這個(gè)力氣通過(guò)getAdapter()來(lái)轉(zhuǎn)換呢?其實(shí)這種機(jī)制可以讓我們將目標(biāo)類轉(zhuǎn)換為它并沒(méi)有實(shí)現(xiàn)的接口。舉個(gè)例子,我們可能想把一個(gè)HashMap當(dāng)作List來(lái)用,盡管這兩個(gè)類的性質(zhì)并不相同,可以這么做:
IAdaptable adaptable = new HashMap();//譯注:這里的HashMap應(yīng)該不是指java.util.HashMap
List list = (List)adaptable.getAdapter(java.util.List.class);
實(shí)現(xiàn)IAdaptable接口
大部分IAdaptable的實(shí)現(xiàn)是一些if語(yǔ)句的疊加,比如我們現(xiàn)在要實(shí)現(xiàn)HashMap的getAdapter()方法,它看起來(lái)可能是這樣:
public class HashMap implements IAdaptable {
public Object getAdapter(Class clazz) {
if (clazz == java.util.List.class) {
List list = new ArrayList(this.size());
list.addAll(this.values());
return list;
}
return null;
}
//
}
所做的就是返回一個(gè)適配器(adapter,更確切的說(shuō)是一個(gè)副本),而不是進(jìn)行直接的類型轉(zhuǎn)換。如果參數(shù)類型沒(méi)有被支持,慣例是返回null值(而非拋出異常),代表這個(gè)方法失敗了。因此,在調(diào)用這個(gè)方法時(shí),不應(yīng)該假定它總是返回非null值。
PlatformObject
當(dāng)然,如果你希望增加一個(gè)新的被支持的adapter類型時(shí)必須編輯這個(gè)類才行(譯注:在getAdapter()里增加更多的if語(yǔ)句),這會(huì)比較辛苦。而且,既然你已經(jīng)知道了這個(gè)類型,何不直接修改接口聲明呢?其實(shí)有很多原因使得你并不希望直接編輯這個(gè)類(例如更容易保持向下兼容性),也不想改變它的類型(HashMap雖然不是一個(gè)List,但可以轉(zhuǎn)換過(guò)去)。
Eclipse通過(guò)PlatformObject抽象類來(lái)解決以上問(wèn)題,它為你實(shí)現(xiàn)了IAdaptable接口,Eclipse平臺(tái)(Platform)提供了IAdapterManager的一個(gè)實(shí)現(xiàn),并且可以通過(guò)Platform.getAdapterManager()訪問(wèn)到,它把所有對(duì)getAdapter()的請(qǐng)求(調(diào)用)委托給一個(gè)名為IAdapterManager的東西。你可以將它想象為一個(gè)巨大的保存著類和adapter信息的Map,而PlatformObject的getAdapter()方法會(huì)查找這個(gè)Map。
適配已存在的類
這樣,PlatformObject不需要重新編譯就能夠支持新的adapter類型,這一點(diǎn)在Eclipse里被大量使用以支持workspace的擴(kuò)展點(diǎn)。
現(xiàn)在假設(shè)我們想要將一個(gè)只包含String類型元素的List轉(zhuǎn)換為一個(gè)XMl節(jié)點(diǎn),這個(gè)節(jié)點(diǎn)的格式如下:
<List>
<Entry>First String</Entry>
<Entry>Second String</Entry>
<Entry>Third String</Entry>
</List>
因?yàn)閠oString()方法可能有其他用途,我們不能通過(guò)覆蓋toString()方法來(lái)實(shí)現(xiàn)這個(gè)功能。所以,我們要給List關(guān)聯(lián)一個(gè)工廠類以處理XML節(jié)點(diǎn)類型的適配請(qǐng)求。要管理工廠類需要以下三個(gè)步驟:
1、由List生成一個(gè)Node,我們把這個(gè)轉(zhuǎn)換過(guò)程用IAdapterFactory包裝起來(lái):
import nu.xom.*;
public class NodeListFactory implements IAdapterFactory {
/**//* 可以轉(zhuǎn)換到的類型 */
private static final Class[] types = {
Node.class,
};
public Class[] getAdapterList() {
return types;
}
/**//* 轉(zhuǎn)換到Node的功能代碼 */
public Object getAdapter(Object list, Class clazz) {
if (clazz == Node.class && list instanceof List) {
Element root = new Element("List");
Iterator it = list.iterator();
while(it.hasNext()) {
Element item = new Element("Entry");
item.appendChild(it.next().toString());
root.appendChild(item);
}
return root;
} else {
return null;
}
}
}
2、把這個(gè)工廠類注冊(cè)到Platform的AdapterManager,這樣當(dāng)我們希望從List的實(shí)例中獲得一個(gè)Node實(shí)例時(shí),就會(huì)找到我們的工廠類。注冊(cè)一個(gè)工廠類的方式也很簡(jiǎn)單:
Platform.getAdapterManager().registerAdapters(
new NodeListFactory(), List.class
);
這條語(yǔ)句將NodeListFactory關(guān)聯(lián)到List類型。當(dāng)從List里請(qǐng)求adapter時(shí),Platform的AdapterManager會(huì)找到NodeListFactory,因?yàn)樵诤笳叩膅etAdapterList()方法的返回結(jié)果里包含了Node類,所以它知道從List實(shí)例得到一個(gè)Node實(shí)例是可行的。在Eclipse里,這個(gè)注冊(cè)步驟一般是在plugin啟動(dòng)時(shí)完成的,但也可以通過(guò)org.eclipse.core.runtime.adapters擴(kuò)展點(diǎn)來(lái)完成。
3、從List獲得Node,下面是例子代碼:
Node getNodeFrom(IAdaptable list) {
Object adaptable = list.getAdapter(Node.class);
if (adaptable != null) {
Node node = (Node)adaptable;
return node;
}
return null;
}
總結(jié)
綜上所述,要在運(yùn)行時(shí)為一個(gè)已有的類增加功能,所要做的只是定義一個(gè)用來(lái)轉(zhuǎn)換的工廠類,然后把它注冊(cè)到Platform的AdapterManager即可。這種方式在保持UI組件和非UI組件的分離方面特別有用。例如在org.rcpapps.rcpnews.ui和org.rcpapps.rcpnews這兩個(gè)plugin里,前者的IPropertySource需要與后者的數(shù)據(jù)對(duì)象(data object)相關(guān)聯(lián),當(dāng)前者初始化時(shí),它將IPropertySource注冊(cè)到Platform,當(dāng)數(shù)據(jù)對(duì)象在導(dǎo)航器(navigator)里被選中的時(shí)候,屬性視圖里就會(huì)顯示正確的屬性。
顯然,java.util.List并不是PlatformObject的子類,所以如果你希望能夠編譯這里所說(shuō)的例子,必須建立一個(gè)List的子類型。注意,可以直接實(shí)現(xiàn)IAdaptable接口,而非必須繼承PlatformObject抽象類。
public class AdaptableList implements IAdaptable, List {
public Object getAdapter(Class adapter) {
return Platform.getAdapterManager().getAdapter(this, adapter);
}
private List delegate = new ArrayList();
public int size() {
return delegate.size();
}
//
}
最后,例子里生成XML的部分使用了XOM的類庫(kù).
IAdaptable在Eclipse里是一個(gè)非常重要的接口。對(duì)于Eclipse開(kāi)發(fā)老手來(lái)說(shuō),它就像異常處理和抽象類一樣尋常;但是對(duì)新手而言,它卻令人感到困惑和畏懼。這篇文章將向你解釋IAdaptable到底是什么,以及它在Eclipse里起到的作用。
類型轉(zhuǎn)換
Java是所謂的強(qiáng)類型語(yǔ)言,也就是說(shuō),每個(gè)實(shí)例都對(duì)應(yīng)一個(gè)類型。其實(shí)類型分為兩種:聲明類型和運(yùn)行時(shí)類型(也分別被稱為靜態(tài)類型和動(dòng)態(tài)類型)。像Python這樣的弱類型語(yǔ)言常被稱為無(wú)類型的語(yǔ)言,其實(shí)嚴(yán)格說(shuō)來(lái)不是這樣,因?yàn)槊總€(gè)實(shí)例都對(duì)應(yīng)一個(gè)運(yùn)行時(shí)類型,只是你并不需要聲明這一點(diǎn)而已。
現(xiàn)在回到Java,為了能夠執(zhí)行一個(gè)類的某個(gè)方法,這個(gè)方法必須在聲明類型中可見(jiàn),換句話說(shuō),即使在運(yùn)行時(shí)實(shí)例是某個(gè)子類型,你也只能執(zhí)行那些父類型里定義的方法。
List list = new ArrayList();
list.add("data"); // 正確,add是List里定義的方法
list.ensureCapacity(4); // 不正確,ensureCapacity()只在ArrayList被定義
如果一定要執(zhí)行特定類型的方法,我們必須先強(qiáng)制轉(zhuǎn)換這個(gè)實(shí)例到正確的類型。對(duì)于上面的例子,我們可以將list轉(zhuǎn)換為ArrayList(譯注:原文In this case, we can cast ArrayList to List,懷疑是筆誤),因?yàn)锳rrayList實(shí)現(xiàn)了List接口,你甚至可以在運(yùn)行時(shí)通過(guò)instanceof關(guān)鍵字檢驗(yàn)list是否為ArrayList的一個(gè)實(shí)例。
可擴(kuò)展的接口
不幸的是,一個(gè)類可能并沒(méi)有實(shí)現(xiàn)你需要的接口,這樣就無(wú)法進(jìn)行強(qiáng)制類型轉(zhuǎn)換了。原因有很多,比如只在少數(shù)情況下才需要這個(gè)接口,或者你需要的接口是在另一個(gè)不相關(guān)的庫(kù)里,又或者接口是有了類以后才開(kāi)發(fā)出來(lái)的,等等。
這時(shí)你就需要IAdaptable了。可以把IAdaptable想象為一個(gè)能夠動(dòng)態(tài)進(jìn)行類型轉(zhuǎn)換的途徑。對(duì)比下面的直接類型轉(zhuǎn)換:
Object o = new ArrayList();
List list = (List)o;
換一種方式,我們可以這樣做:
IAdaptable adaptable = new ArrayList();//譯注:這里的ArrayList應(yīng)該不是指java.util.ArrayList
List list = (List)adaptable.getAdapter(java.util.List.class);
這就是上面所說(shuō)的動(dòng)態(tài)類型轉(zhuǎn)換,我們所做的事情是試圖把a(bǔ)daptable轉(zhuǎn)換為一個(gè)List實(shí)例。
那么,當(dāng)可以直接轉(zhuǎn)換的時(shí)候?yàn)槭裁匆M(fèi)這個(gè)力氣通過(guò)getAdapter()來(lái)轉(zhuǎn)換呢?其實(shí)這種機(jī)制可以讓我們將目標(biāo)類轉(zhuǎn)換為它并沒(méi)有實(shí)現(xiàn)的接口。舉個(gè)例子,我們可能想把一個(gè)HashMap當(dāng)作List來(lái)用,盡管這兩個(gè)類的性質(zhì)并不相同,可以這么做:
IAdaptable adaptable = new HashMap();//譯注:這里的HashMap應(yīng)該不是指java.util.HashMap
List list = (List)adaptable.getAdapter(java.util.List.class);
實(shí)現(xiàn)IAdaptable接口
大部分IAdaptable的實(shí)現(xiàn)是一些if語(yǔ)句的疊加,比如我們現(xiàn)在要實(shí)現(xiàn)HashMap的getAdapter()方法,它看起來(lái)可能是這樣:
public class HashMap implements IAdaptable {
public Object getAdapter(Class clazz) {
if (clazz == java.util.List.class) {
List list = new ArrayList(this.size());
list.addAll(this.values());
return list;
}
return null;
}
//
}
所做的就是返回一個(gè)適配器(adapter,更確切的說(shuō)是一個(gè)副本),而不是進(jìn)行直接的類型轉(zhuǎn)換。如果參數(shù)類型沒(méi)有被支持,慣例是返回null值(而非拋出異常),代表這個(gè)方法失敗了。因此,在調(diào)用這個(gè)方法時(shí),不應(yīng)該假定它總是返回非null值。
PlatformObject
當(dāng)然,如果你希望增加一個(gè)新的被支持的adapter類型時(shí)必須編輯這個(gè)類才行(譯注:在getAdapter()里增加更多的if語(yǔ)句),這會(huì)比較辛苦。而且,既然你已經(jīng)知道了這個(gè)類型,何不直接修改接口聲明呢?其實(shí)有很多原因使得你并不希望直接編輯這個(gè)類(例如更容易保持向下兼容性),也不想改變它的類型(HashMap雖然不是一個(gè)List,但可以轉(zhuǎn)換過(guò)去)。
Eclipse通過(guò)PlatformObject抽象類來(lái)解決以上問(wèn)題,它為你實(shí)現(xiàn)了IAdaptable接口,Eclipse平臺(tái)(Platform)提供了IAdapterManager的一個(gè)實(shí)現(xiàn),并且可以通過(guò)Platform.getAdapterManager()訪問(wèn)到,它把所有對(duì)getAdapter()的請(qǐng)求(調(diào)用)委托給一個(gè)名為IAdapterManager的東西。你可以將它想象為一個(gè)巨大的保存著類和adapter信息的Map,而PlatformObject的getAdapter()方法會(huì)查找這個(gè)Map。
適配已存在的類
這樣,PlatformObject不需要重新編譯就能夠支持新的adapter類型,這一點(diǎn)在Eclipse里被大量使用以支持workspace的擴(kuò)展點(diǎn)。
現(xiàn)在假設(shè)我們想要將一個(gè)只包含String類型元素的List轉(zhuǎn)換為一個(gè)XMl節(jié)點(diǎn),這個(gè)節(jié)點(diǎn)的格式如下:
<List>
<Entry>First String</Entry>
<Entry>Second String</Entry>
<Entry>Third String</Entry>
</List>
因?yàn)閠oString()方法可能有其他用途,我們不能通過(guò)覆蓋toString()方法來(lái)實(shí)現(xiàn)這個(gè)功能。所以,我們要給List關(guān)聯(lián)一個(gè)工廠類以處理XML節(jié)點(diǎn)類型的適配請(qǐng)求。要管理工廠類需要以下三個(gè)步驟:
1、由List生成一個(gè)Node,我們把這個(gè)轉(zhuǎn)換過(guò)程用IAdapterFactory包裝起來(lái):
import nu.xom.*;
public class NodeListFactory implements IAdapterFactory {
/**//* 可以轉(zhuǎn)換到的類型 */
private static final Class[] types = {
Node.class,
};
public Class[] getAdapterList() {
return types;
}
/**//* 轉(zhuǎn)換到Node的功能代碼 */
public Object getAdapter(Object list, Class clazz) {
if (clazz == Node.class && list instanceof List) {
Element root = new Element("List");
Iterator it = list.iterator();
while(it.hasNext()) {
Element item = new Element("Entry");
item.appendChild(it.next().toString());
root.appendChild(item);
}
return root;
} else {
return null;
}
}
}
2、把這個(gè)工廠類注冊(cè)到Platform的AdapterManager,這樣當(dāng)我們希望從List的實(shí)例中獲得一個(gè)Node實(shí)例時(shí),就會(huì)找到我們的工廠類。注冊(cè)一個(gè)工廠類的方式也很簡(jiǎn)單:
Platform.getAdapterManager().registerAdapters(
new NodeListFactory(), List.class
);
這條語(yǔ)句將NodeListFactory關(guān)聯(lián)到List類型。當(dāng)從List里請(qǐng)求adapter時(shí),Platform的AdapterManager會(huì)找到NodeListFactory,因?yàn)樵诤笳叩膅etAdapterList()方法的返回結(jié)果里包含了Node類,所以它知道從List實(shí)例得到一個(gè)Node實(shí)例是可行的。在Eclipse里,這個(gè)注冊(cè)步驟一般是在plugin啟動(dòng)時(shí)完成的,但也可以通過(guò)org.eclipse.core.runtime.adapters擴(kuò)展點(diǎn)來(lái)完成。
3、從List獲得Node,下面是例子代碼:
Node getNodeFrom(IAdaptable list) {
Object adaptable = list.getAdapter(Node.class);
if (adaptable != null) {
Node node = (Node)adaptable;
return node;
}
return null;
}
總結(jié)
綜上所述,要在運(yùn)行時(shí)為一個(gè)已有的類增加功能,所要做的只是定義一個(gè)用來(lái)轉(zhuǎn)換的工廠類,然后把它注冊(cè)到Platform的AdapterManager即可。這種方式在保持UI組件和非UI組件的分離方面特別有用。例如在org.rcpapps.rcpnews.ui和org.rcpapps.rcpnews這兩個(gè)plugin里,前者的IPropertySource需要與后者的數(shù)據(jù)對(duì)象(data object)相關(guān)聯(lián),當(dāng)前者初始化時(shí),它將IPropertySource注冊(cè)到Platform,當(dāng)數(shù)據(jù)對(duì)象在導(dǎo)航器(navigator)里被選中的時(shí)候,屬性視圖里就會(huì)顯示正確的屬性。
顯然,java.util.List并不是PlatformObject的子類,所以如果你希望能夠編譯這里所說(shuō)的例子,必須建立一個(gè)List的子類型。注意,可以直接實(shí)現(xiàn)IAdaptable接口,而非必須繼承PlatformObject抽象類。
public class AdaptableList implements IAdaptable, List {
public Object getAdapter(Class adapter) {
return Platform.getAdapterManager().getAdapter(this, adapter);
}
private List delegate = new ArrayList();
public int size() {
return delegate.size();
}
//
}
最后,例子里生成XML的部分使用了XOM的類庫(kù).