在上回的blog中,我抱怨過用Java用內(nèi)部類來實現(xiàn)事件回調(diào)的機制是多么難看和麻煩。在這段時間里,我一直在考慮是否有什么方法可以不用內(nèi)部類而實現(xiàn)同樣的效果。因為Java語言本身的限制,所以常規(guī)方法是行不通的。有人建議用反射——的確通過反射可以調(diào)用任意的方法,但是反射效率不佳,對頻繁發(fā)生的事件或許不太合適。動態(tài)代理也不能解決方法映射的難題。我似乎走進了死胡同。
既然此路不通,那么C#是如何實現(xiàn)delegate的呢?過去也曾聽聞過一些內(nèi)幕,不過這次被逼才真的下決心認真去看這方面的東西。原來M$使用的是代碼生成的技術(shù):對于每個delegate,C#都會為它生成一個派生于MulticaseDelegate的對象,其中實現(xiàn)了一個和delegate簽名相同的方法。同時,對delegate的操作符+=和-=也會被編譯器處理成對MulticaseDelegate方法的調(diào)用。
知道了這一點,接下來就需要看看Java中有沒有類似的代碼生成技術(shù)了。有意思的是,查找的時候發(fā)現(xiàn)有消息說,Java 6.0(Mutang)中將會提供動態(tài)代碼生成的功能。這的確很吸引人,不過Java6還在Beta階段,眼下還指望不上。其他比較出名的方法就是Apache becl和Objectweb ASM了。這兩個庫都比較底層,不過還有一個開源的項目——cglib——它在內(nèi)部使用了asm,不過提供了較多的實用功能。據(jù)說Hibernate和Spring都用到了這個東西。研究這個庫的時候,我一眼看到了MethodDelegate類——很明顯這就是我要找的東西了。
MethodDelegate的設(shè)計思想很類似于C#的delegate——將接口調(diào)用轉(zhuǎn)發(fā)給類的一個成員函數(shù)。不過閱讀文檔的時候我發(fā)現(xiàn)一個問題。MethodDelegate要求其所實現(xiàn)的接口必須只有一個公共方法,但是SWT中的許多事件接口都有不止一個方法;比如,SelectionListener就有widgetSelected和idgetDefaultSelected兩個方法。因此要在SWT中使用MethodDelegate,還
必須再多實現(xiàn)另外一層轉(zhuǎn)發(fā)。
了解手段,接下來的事情就不難了。總結(jié)起來,需要的步驟大致如下:
1、為每種需要實現(xiàn)的事件聲明一個接口。這是MethodDelegate的要求。
2、用一個類實現(xiàn)SWT的事件接口,并將特定的接口調(diào)用轉(zhuǎn)發(fā)到第一步所實現(xiàn)
的接口。
3、用MethodDelegate提供的方法,聲明事件處理對象(Event Handler
Target,通常為主窗體或主部件)要實現(xiàn)上述的事件接口。下面就來實現(xiàn)一下。為了簡單起見,將需要實現(xiàn)的接口聲明為事件轉(zhuǎn)發(fā)類的內(nèi)部接口,以避免維護太多接口文件(因為該接口只需要聲明一個方法,所以不會把外部類搞得太過復(fù)雜。)例如,處理部件選擇事件(widgetSelected)的類可以如下實現(xiàn):
package org.yuhao.swt.events;
import net.sf.cglib.reflect.MethodDelegate;
import org.eclipse.swt.events.*;
public class WidgetSelectedHandler implements SelectionListener
{
?public WidgetSelectedHandler( Object target, String
methodName )
?{
??delegate = (IWidgetSelectedDelegate)
MethodDelegate.create( target,
????methodName,
IWidgetSelectedDelegate.class );
?}
?public void widgetDefaultSelected( SelectionEvent e )
?{
?}
?public void widgetSelected( SelectionEvent e )
?{
??delegate.invoke( e );
?}
?public void invoke( SelectionEvent e )
?{
?}
?public interface IWidgetSelectedDelegate
?{
??void invoke( SelectionEvent e );
?}
?private IWidgetSelectedDelegate delegate;
}
這個接口雖然只有外部類用到,但是必須聲明為public的,否則運行會出錯(我想大概是因為代碼生成以后還是外部類,需要公開訪問權(quán)限)。為了簡化調(diào)用,再聲明一個處理事件的輔助類EventHandler,專門管理將各種事件轉(zhuǎn)發(fā)到相應(yīng)的Handler的工作:
public class EventHandler
{
?public EventHandler( Object target )
?{
??this.target = target;
?}
?public void handleSelected( Button btn, String methodName )
?{
??btn.addSelectionListener( new
WidgetSelectedHandler( target, methodName ) );
?}
?private Object target;
}
這樣,在窗口中就可以簡單的如下處理事件:
public MainShell extends Shell()
{
??public MainShell( Display display )
?{
??......
??handler = new EventHandler( this );
??handler.handleSelected( btn, "btn_clicked" );
?}
?public void btn_clicked( SelectionEvent e )
?{
??...
?}
}
這里還需要注意:1、任何事件處理方法必須聲明為public的。這樣似乎有違面向?qū)ο蟮姆庋b原則,不過實際上并不會造成什么大問題。2、事件方法的簽名必須和對應(yīng)的事件方法相同。例如,widgetSelected方法有一個SelectionEvent參數(shù),那么處理該事件的btn_clicked方法也必須有且只有這一個參數(shù)。如果寫錯了,那么運行的時候會拋出異常,說找不到指定的方法。這還是需要程序員的細心來保證。還算幸運的是出錯的提示非常明顯,不必擔心使用了過度復(fù)雜的技術(shù)而找不到真正的出錯點。