1. 簡介和簡單的實現(xiàn)
IAdapteable實際上在Eclipse早期版本中不叫這個名字,它原來的名字叫做IExtensible,顧名思義就是可以擴展的意思,后來為了更能突出是由一個類配適到一個接口這么一種機制,所以改名為IAdaptable。
這個接口有什么用呢,其實說白了,就是提供一個類型的轉(zhuǎn)換機制。比如下面這段代碼:
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,并且實現(xiàn)了IAdaptable接口,我們想要將它轉(zhuǎn)化成Vector類型對象,于是在getAdapter方法中我們判斷
傳入?yún)?shù)類型,如果是Vector類那么就新生成一個Vector對象,將ArrayList中的值全部賦給它,并返回。
這樣,我們就可以寫出以下代碼:
Vector v = (Vector) list.getAdapter(Vector. class );
ArrayList會返回Vector對象,這個對象是ArrayList的一個另外一種類型的副本。
2.一個Swing程序
讀者會問:這有什么用啊,不就簡單轉(zhuǎn)化一下麼。其實說實話,從上面的代碼來看確實沒什么用,但是如果我們換一個場景試試。
寫
這么一個Swing程序:有一個對話框,其中它有一個ComboBox和一個Table,ComboBox中存放的是一個名為Person類型的對象,當
ComboBox的選項發(fā)生改變的時候,就在Table上顯示它的屬性,我們假設(shè)這個Swing程序已經(jīng)在某個項目中開始實施,并且其界面布局不易更改。
看看代碼:
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);
}
});
}
運行我們的代碼,會發(fā)現(xiàn)效果還可以,每當我們選項改變的時候,Table就如同一個屬性欄一樣,改變著自己的內(nèi)容:
3.需求變更
OK,問題來了。我寫完這段代碼后,組長告訴我,現(xiàn)在我們有一個新的需求,就是Combox中不僅僅有Person類型存在,而且還有一些貨物(Product)類型,也就是說,我的table顯示屬性不能光針對Person這個類型了,還需要顯示Product的屬性。
我心里罵了句:早TMD干嘛了,都快交活兒了才告訴我。
無奈,我新增加了一個Product類型,然后更改了ActionListener中的部分代碼:
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);
結(jié)果還是讓人滿意的:
后來我感覺ActionListener代碼有一些凌亂,又封裝了一個Builder類,讓它創(chuàng)建TableModel:
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,顯示當前同種類型直接的層次結(jié)構(gòu),等等。
我聽了領(lǐng)導嘮叨半個小時后,打開了我剛寫的Builder類,往里面增加著我的代碼……
類圖大致如下:
程序經(jīng)過修改后,好不容易又符合要求了,情況又發(fā)生了變化,組長需要我繼續(xù)修改。我無奈地看著組長,組長也無奈地看著我那用if-else堆成的代碼……
“悲哀,真讓我替你感到悲~哀!”組長操著本山的腔調(diào)這樣對我說。
是啊,多悲哀啊,一個設(shè)計上的錯誤讓我的代碼無法適應(yīng)需求的變化。
好了,讓我們回到IAdaptable上。
通過上面的例子,我看可以發(fā)現(xiàn)這么一個情況:同樣一個對象,在程序里面往往有許多不同的顯示方式(不僅僅是在UI顯示,在其他一些代碼里,需要轉(zhuǎn)化成另外類型或者數(shù)據(jù)結(jié)構(gòu))。
如果我用IAdapteable的思想來實現(xiàn)剛才的Swing屬性顯示,會怎么樣呢?
重新寫一遍ActionListener中的代碼:
Object obj = comboBox.getSelectedItem();
TableModel jTable1Model = null ;
if (obj instanceof IAdaptable){
jTable1Model = (TableModel) ((IAdaptable)obj).getAdapter(TableModel. class );
}
table.setModel(jTable1Model);
然后分別讓Person和Product實現(xiàn)IAdaptable接口:
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),現(xiàn)在是作為擴展類接口抽取了出來,而那些需要在Table上顯示自己屬性的業(yè)務(wù)模型
(Person,Product)實現(xiàn)了IAdaptable接口,將顯示模型(TableModel)作為了自己的擴展接口類型給予實例返回,并且UI
代碼中,Table和業(yè)務(wù)模型之間形成一種契約:凡是實現(xiàn)了IAdaptable的接口才可以獲得在該Table上顯示的資格,并且Table從
IAdaptable的getAdapter方法獲得顯示模型:
這樣一來,我們的Swing程序不僅功能能夠?qū)崿F(xiàn),而且UI部分代碼和業(yè)務(wù)模型代碼之間的耦合性減小了。
而
且,如果需求發(fā)生變化,比如像剛才提到那樣“需要增加一個Tree,顯示當前同種類型直接的層次結(jié)構(gòu)”,那我們就在getAdaper方法中返回一個
TreeModel的副本,然后在UI中增加一個Tree,讓它像Table一樣,從IAdaptable接口中取出我們的TreeModel即可——
UI擴展也變得容易起來。
現(xiàn)在我可以對組長說:讓需求變化來得更猛烈些吧!
5.模型代碼無法修改
有這樣一個問題:如果我們的模型已經(jīng)存在,而且代碼已經(jīng)無法修改了怎么辦?
IAdapterFactory就是為這種情況準備的。
先看看IAdapterFactory:
public Object getAdapter(Object adapter,Class clazz);
}
這里面的方法和IAdaptable差不多,只是多了一個參數(shù),這個參數(shù)就是需要我們返回Adapter接口的對象。
在Eclipse中IAdapterFactory并不是單獨存在的,而是有一個IAdapterManager對它進行維護的:
public Object getAdapter(Object adapter,Class clazz);
public boolean registerAdapters (Class clazz,IAdaptableFactory factory);
}
現(xiàn)在讓我們這樣來修改剛才的Swing程序:
假設(shè)Product類型是第三方提供的jar包,我們已經(jīng)無法修改它的代碼了,那我們就需要用到IAdapableFactory的擴展方法。請看下面的代碼
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;
}
}
有了這兩個實現(xiàn)類后,我們再去修改一下ActionListener中的代碼:
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);
好了,只要我們在適當?shù)牡胤?,將IAdaptableFactory注冊進IAdaptaerManager,那我們對無法修改代碼的業(yè)務(wù)模型也能進行接口的擴展了。
6.結(jié)束語
在Eclipse中,IAdaptable的應(yīng)用非常廣泛,而且如果實現(xiàn)了IAdaptable接口的類被成為Platform Object,可見IAdaptable在Eclipse框架中的分量。本人的知識有限,如果有遺漏或者錯誤的地方,還請各位讀者指出。