月蚀传说

浮躁让人失去理智
posts - 25, comments - 101, trackbacks - 0, articles - 0
  BlogJava :: 首页 ::  :: 联系 :: 聚合  :: 管理

IAdaptable和IAdaptableFactory

Posted on 2006-10-09 19:08 Dart 阅读(1802) 评论(3)  编辑  收藏 所属分类: Eclipse

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框架中的分量。本人的知识有限,如果有遗漏或者错误的地方,还请各位读者指出。


评论

# re: IAdaptable和IAdaptableFactory  回复  更多评论   

2007-03-27 16:53 by iceman
写得清楚,谢谢

# re: IAdaptable和IAdaptableFactory  回复  更多评论   

2007-05-17 11:40 by re
看不到图片了

# re: IAdaptable和IAdaptableFactory  回复  更多评论   

2011-03-03 08:48 by d
写得很好的。

只有注册用户登录后才能发表评论。


网站导航: