1.?簡介和簡單的實現
IAdapteable實際上在Eclipse早期版本中不叫這個名字,它原來的名字叫做IExtensible,顧名思義就是可以擴展的意思,后來為了更能突出是由一個類配適到一個接口這么一種機制,所以改名為IAdaptable。
這個接口有什么用呢,其實說白了,就是提供一個類型的轉換機制。比如下面這段代碼:
Class?IAdaptable??
public
?
interface
?IAdaptable??{
?
public
?Object?getAdapter(Class?clazz);
}
Class?ListAdapter
public
?
class
?ListAdapter?
extends
?ArrayList?
implements
?IAdaptable?
{
?
public
?Object?getAdapter(Class?clazz)?{
??
if
(clazz?
==
?Vector.
class
){
???Vector?v?
=
?
new
?Vector(
this
.size());
???v.addAll(
this
);
???
return
?v;
??}
??
return
?
null
;
?}
}
ListAdapter類繼承了ArrayList,并且實現了IAdaptable接口,我們想要將它轉化成Vector類型對象,于是在getAdapter方法中我們判斷傳入參數類型,如果是Vector類那么就新生成一個Vector對象,將ArrayList中的值全部賦給它,并返回。
這樣,我們就可以寫出以下代碼:
ListAdapter?list?
=
?
new
?ListAdapter();
??Vector?v?
=
?(Vector)?list.getAdapter(Vector.
class
);
ArrayList會返回Vector對象,這個對象是ArrayList的一個另外一種類型的副本。
2.一個Swing程序
讀者會問:這有什么用啊,不就簡單轉化一下麼。其實說實話,從上面的代碼來看確實沒什么用,但是如果我們換一個場景試試。
寫這么一個Swing程序:有一個對話框,其中它有一個ComboBox和一個Table,ComboBox中存放的是一個名為Person類型的對象,當ComboBox的選項發生改變的時候,就在Table上顯示它的屬性,我們假設這個Swing程序已經在某個項目中開始實施,并且其界面布局不易更改。
看看代碼:
Class?person
public
?
class
?Person?{
?
private
?String?name?
=
?
"
name
"
;
?
private
?String?age?
=
?
"
23
"
;
?
private
?String?sex?
=
?
"
male
"
;
?
?
public
?Person(String?name){
??
this
.setName(name);
?}
?
?
public
?String?getName()?{
??
return
?name;
?}
?
?
public
?
void
?setName(String?name)?{
??
this
.name?
=
?name;
?}
?……
}
UI類的部分代碼:
{
???????table?
=
?
new
?JTable();
???????
this
.getContentPane().add(table);
???????table.setBounds(
218
,?
2
,?
171
,?
248
);
??????}
??????{
???????ComboBoxModel?jComboBox1Model?
=
?
new
?DefaultComboBoxModel(
????????
new
?Object[]?{?
new
?Person(
"
rEloaD
"
),?
new
?Person(
"
b
"
)?});
???????comboBox?
=
?
new
?JComboBox();
???????
this
.getContentPane().add(comboBox);
???????comboBox.setModel(jComboBox1Model);?
???????comboBox.addActionListener(
new
?ActionListener(){
?????????????????
public
?
void
?actionPerformed(ActionEvent?e){
?????????????????????JComboBox?comboBox?
=
(JComboBox)e.getSource();
?????????????????????Person?p?
=
?(Person)comboBox.getSelectedItem();
?????????????????????TableModel?jTable1Model?
=
?
new
?DefaultTableModel(
??????????????????????????????????? ?
new
?String[][]?{?{?
"
Name
"
,?p.getName()?},
????????????????????????????????????????????????????? ?{?
"
Sex
"
,?p.getSex()?},
???????????????????????????????????????????????????? ?{?
"
Age
"
,?p.getAge()?}},
?????????????????????????????????? ??
new
?String[]?{?
"
Column?1
"
,?
"
Column?2
"
?});
?????????????????????table.setModel(jTable1Model);
??????????????????????}
??????????????????});
??????}
?運行我們的代碼,會發現效果還可以,每當我們選項改變的時候,Table就如同一個屬性欄一樣,改變著自己的內容:
3.需求變更
OK,問題來了。我寫完這段代碼后,組長告訴我,現在我們有一個新的需求,就是Combox中不僅僅有Person類型存在,而且還有一些貨物(Product)類型,也就是說,我的table顯示屬性不能光針對Person這個類型了,還需要顯示Product的屬性。
我心里罵了句:早TMD干嘛了,都快交活兒了才告訴我。
無奈,我新增加了一個Product類型,然后更改了ActionListener中的部分代碼:
JComboBox?comboBox?
=
(JComboBox)e.getSource();
?Object?obj?
=
?comboBox.getSelectedItem();
?TableModel?jTable1Model?
=
?
null
;
??
if
(obj?
instanceof
?Person){
??????jTable1Model?
=
?
new
?DefaultTableModel(
??????????????????????? ??
new
?String[][]?{?{?
"
Name
"
,?((Person)obj).getName()?},
????????????????????????????????????????? ?{?
"
Sex
"
,?((Person)obj).getSex()?},
?????????????????????????????????????????? ?{?
"
Age
"
,?((Person)obj).getAge()?}},
????????????????????????? ?
new
?String[]?{?
"
Column?1
"
,?
"
Column?2
"
?});
??}
??
if
(obj?
instanceof
?Product){
??????jTable1Model?
=
?
new
?DefaultTableModel(
????????????????????????
new
?String[][]?{?{?
"
Name
"
,?((Product)obj).name?},
???????????????????????????????????????? ?{?
"
price
"
,?((Product)obj).price?},
???????????????????????????????????????? ?{?
"
quantity
"
,?((Product)obj).quantity?}},
?????????????????????? ??
new
?String[]?{?
"
Column?1
"
,?
"
Column?2
"
?});
??}
??table.setModel(jTable1Model);
結果還是讓人滿意的:
后來我感覺ActionListener代碼有一些凌亂,又封裝了一個Builder類,讓它創建TableModel:
public
?
static
?TableModel?modelBuilder(Object?obj){
TableModel?jTable1Model?=?null;
??
if
(obj?
instanceof
?Person){
??????????jTable1Model?
=
?
new
?DefaultTableModel(
?????????????
new
?String[][]?{?{?
"
Name
"
,?((Person)obj).getName()?},
???????????????{?
"
Sex
"
,?((Person)obj).getSex()?},
???????????????{?
"
Age
"
,?((Person)obj).getAge()?}},
?????????????
new
?String[]?{?
"
Column?1
"
,?
"
Column?2
"
?});
??????????????????????????}
??????????????????????????
if
(obj?
instanceof
?Product){
????????????????????????????jTable1Model?
=
?
new
?DefaultTableModel(
???????????????
new
?String[][]?{?{?
"
Name
"
,?((Product)obj).name?},
?????????????????{?
"
price
"
,?((Product)obj).price?},
?????????????????{?
"
quantity
"
,?((Product)obj).quantity?}},
???????????????
new
?String[]?{?
"
Column?1
"
,?
"
Column?2
"
?});
??}
return
?jTable1Model;
}
我對自己的代碼還算滿意,至少目前能用了。
4.需求又變了
第二天,組長告訴我,需求又變了,這會不但多增加一個“服裝”類型,Product類型屬性顯示有錯誤,并且需要增加一個Tree,顯示當前同種類型直接的層次結構,等等。
我聽了領導嘮叨半個小時后,打開了我剛寫的Builder類,往里面增加著我的代碼……
類圖大致如下:
程序經過修改后,好不容易又符合要求了,情況又發生了變化,組長需要我繼續修改。我無奈地看著組長,組長也無奈地看著我那用if-else堆成的代碼……
“悲哀,真讓我替你感到悲~哀!”組長操著本山的腔調這樣對我說。
是啊,多悲哀啊,一個設計上的錯誤讓我的代碼無法適應需求的變化。
好了,讓我們回到IAdaptable上。
通過上面的例子,我看可以發現這么一個情況:同樣一個對象,在程序里面往往有許多不同的顯示方式(不僅僅是在UI顯示,在其他一些代碼里,需要轉化成另外類型或者數據結構)。
如果我用IAdapteable的思想來實現剛才的Swing屬性顯示,會怎么樣呢?
重新寫一遍ActionListener中的代碼:
JComboBox?comboBox?
=
(JComboBox)e.getSource();
Object?obj?
=
?comboBox.getSelectedItem();
TableModel?jTable1Model?
=
?
null
;
if
(obj?
instanceof
?IAdaptable){
???????jTable1Model?
=
?(TableModel)?((IAdaptable)obj).getAdapter(TableModel.
class
);
}
table.setModel(jTable1Model);
然后分別讓Person和Product實現IAdaptable接口:
Class?Person:
public
?
class
?Person?
implements
?IAdaptable{
???…..
???
public
?Object?getAdapter(Class?clazz)?{
??
if
(clazz?
==
?TableModel.
class
){
????
return
?
new
?DefaultTableModel(
??????
new
?String[][]?{?{?
"
Name
"
,?getName()?},
????????{?
"
Sex
"
,?getSex()?},
????????{?
"
Age
"
,?getAge()?}},
??????
new
?String[]?{?
"
Column?1
"
,?
"
Column?2
"
?});
??}
??
return
?
null
;
?}
}
Class?Product
public
?
class
?Product?
implements
?IAdaptable{
?……
????
public
?Object?getAdapter(Class?clazz)?{
??
if
(clazz?
==
?TableModel.
class
){
????
return
?
new
?DefaultTableModel(
??????
new
?String[][]?{?{?
"
Name
"
,?getName()?},
????????{?
"
Sex
"
,?getSex()?},
????????{?
"
Age
"
,?getAge()?}},
??????
new
?String[]?{?
"
Column?1
"
,?
"
Column?2
"
?});
??}
??
return
?
null
;
?}
}
其實我們的代碼量并沒有任何的改變,前后都是一樣的。
但是我們將Table需要顯示的模型(TableModel),現在是作為擴展類接口抽取了出來,而那些需要在Table上顯示自己屬性的業務模型(Person,Product)實現了IAdaptable接口,將顯示模型(TableModel)作為了自己的擴展接口類型給予實例返回,并且UI代碼中,Table和業務模型之間形成一種契約:凡是實現了IAdaptable的接口才可以獲得在該Table上顯示的資格,并且Table從IAdaptable的getAdapter方法獲得顯示模型:
這樣一來,我們的Swing程序不僅功能能夠實現,而且UI部分代碼和業務模型代碼之間的耦合性減小了。
而且,如果需求發生變化,比如像剛才提到那樣“需要增加一個Tree,顯示當前同種類型直接的層次結構”,那我們就在getAdaper方法中返回一個TreeModel的副本,然后在UI中增加一個Tree,讓它像Table一樣,從IAdaptable接口中取出我們的TreeModel即可——UI擴展也變得容易起來。
現在我可以對組長說:讓需求變化來得更猛烈些吧!
5.模型代碼無法修改
有這樣一個問題:如果我們的模型已經存在,而且代碼已經無法修改了怎么辦?
IAdapterFactory就是為這種情況準備的。
先看看IAdapterFactory:
public
?
interface
?IAdaptableFactory?{
?
public
?Object?getAdapter(Object?adapter,Class?clazz);
}
這里面的方法和IAdaptable差不多,只是多了一個參數,這個參數就是需要我們返回Adapter接口的對象。
在Eclipse中IAdapterFactory并不是單獨存在的,而是有一個IAdapterManager對它進行維護的:
public
?
interface
?IAdaptableManager?{
?
public
?Object?getAdapter(Object?adapter,Class?clazz);
?
public
?
boolean
?registerAdapters?(Class?clazz,IAdaptableFactory?factory);
}
現在讓我們這樣來修改剛才的Swing程序:
假設Product類型是第三方提供的jar包,我們已經無法修改它的代碼了,那我們就需要用到IAdapableFactory的擴展方法。請看下面的代碼
Class?AdaptableFactoryImpl
public
?
class
?AdaptableFactoryImpl?
implements
?IAdaptableFactory?{
?
public
?Object?getAdapter(Object?adapter,?Class?clazz)?{
??
if
(adapter?
instanceof
?Product){
???
if
(clazz?
==
TableModel.
class
){
????
return
?
new
?DefaultTableModel(
??????
new
?String[][]?{?{?
"
Name
"
,((Product)adapter).name?},
????????{?
"
price
"
,?((Product)adapter).price?},
????????{?
"
quantity
"
,?((Product)adapter).quantity?}},
??????
new
?String[]?{?
"
Column?1
"
,?
"
Column?2
"
?});
???}
??}
??
return
?
null
;
?}
?
public
?Class[]?getAdapterList()?{
??
return
?
new
?Class[]{TableModel.
class
};
?}
}
Class?AdapterManagerImpl:
public
?
class
?AdapterManagerImpl?
implements
?IAdaptableManager?{
?
private
?
static
?AdapterManagerImpl?instance?
=
?
null
;
?
private
?Hashtable?table?
=
?
new
?Hashtable();
?
?
private
?AdapterManagerImpl(){}
?
?
public
?Object?getAdapter(Object?adapter,?Class?clazz)?{
??Object?factory?
=
?table.get(adapter.getClass());
??
if
(factory?
!=
?
null
){
???
return
?((IAdaptableFactory)factory).getAdapter(adapter,clazz);
??}
??
return
?
null
;
?}
?
public
?
boolean
?registerFacotry(Class?clazz,?IAdaptableFactory?factory)?{
??
try
{
???table.put(clazz,factory);
???
return
?
true
;
??}
catch
(Exception?e){
???
return
?
false
;
??}
?}
?
public
?
synchronized
?
static
?AdapterManagerImpl?getInstance()?{
??
if
(instance?
==
?
null
)?instance?
=
?
new
?AdapterManagerImpl();
??
return
?instance;
?}
}
有了這兩個實現類后,我們再去修改一下ActionListener中的代碼:
???????? JComboBox?comboBox?
=
?(JComboBox)?e.getSource();
?????????Object?obj?
=
?comboBox.getSelectedItem();
?????????TableModel?jTable1Model?
=
?
null
;
?????????
if
?(obj?
instanceof
?IAdaptable)?{
??????????jTable1Model?
=
?(TableModel)?((IAdaptable)?obj)
????????????.getAdapter(TableModel.
class
);
?????????}?
else
?{
??????????jTable1Model?
=
?(TableModel)?AdapterManagerImpl
????????????.getInstance().getAdapter(obj,
??????????????TableModel.
class
);
?????????}
?????????table.setModel(jTable1Model);
好了,只要我們在適當的地方,將IAdaptableFactory注冊進IAdaptaerManager,那我們對無法修改代碼的業務模型也能進行接口的擴展了。
6.結束語
在Eclipse中,IAdaptable的應用非常廣泛,而且如果實現了IAdaptable接口的類被成為Platform Object,可見IAdaptable在Eclipse框架中的分量。本人的知識有限,如果有遺漏或者錯誤的地方,還請各位讀者指出。