由于項(xiàng)目需求岔冀,需要為Java提供一套支持事件驅(qū)動(dòng)機(jī)制的類庫(kù)偎快,可以實(shí)現(xiàn)類似于C#中的event和delegate機(jī)制。眾所周知目代,Java語(yǔ)言本身以及其標(biāo)準(zhǔn)庫(kù)中并沒(méi)有提供事件驅(qū)動(dòng)機(jī)制的相關(guān)接口焚刚,雖然Swing(我且認(rèn)為其不屬于標(biāo)準(zhǔn)庫(kù)点弯,因?yàn)橐话銢](méi)人用:)中存在相關(guān)的類支持該機(jī)制以實(shí)現(xiàn)組件的事件處理,但它畢竟是與GUI相耦合的矿咕,而在其它類型的應(yīng)用程序中使用起來(lái)顯得就有些別扭抢肛,缺乏通用性狼钮。因此有必要實(shí)現(xiàn)一套通用的Java事件驅(qū)動(dòng)機(jī)制類庫(kù),然后將其應(yīng)用于通用的Java應(yīng)用程序當(dāng)中捡絮,雖然這并不是什么難事:)
讓我們先考察一下C#的事件驅(qū)動(dòng)機(jī)制編寫(xiě)方法熬芜。C#中提供的event關(guān)鍵字可以很容易的用來(lái)定義一個(gè)事件,然后通過(guò)向事件中添加事件處理函數(shù)(在C#中一般用委托(delegate)來(lái)引用一個(gè)函數(shù))福稳,觸發(fā)事件就可以調(diào)用相關(guān)的處理函數(shù)涎拉,也即是事件驅(qū)動(dòng)的過(guò)程。例如:
//定義事件和對(duì)應(yīng)的委托
public event MyDelegate Click;
public delegate void MyDelegate();
//定義委托
void OnClick(){
console.writeline("you just clicked me!");
}
//將委托與事件關(guān)聯(lián)
Click += OnClick;
//觸發(fā)事件
Click();
上面的代碼就是用C#實(shí)現(xiàn)的事件驅(qū)動(dòng)機(jī)制的一個(gè)簡(jiǎn)單的例子的圆,可見(jiàn)是非常簡(jiǎn)單的鼓拧,這都源于C#在語(yǔ)言層面(其實(shí)是CLR)提供的便利。遺憾的是Java并不提供這樣的便利越妈,需要人為去實(shí)現(xiàn)季俩。下面本文將提供兩種實(shí)現(xiàn)事件驅(qū)動(dòng)機(jī)制的方法,僅供參考梅掠。
- 觀察者模式
觀察者模式是一種常用的設(shè)計(jì)模式酌住,觀察者(Observer)先通過(guò)訂閱被觀察對(duì)象(Subject),這樣一旦被觀察者(Subject)發(fā)生某種變化阎抒,就會(huì)將變化通知觀察者(Observer)赂韵。
這種設(shè)計(jì)模式剛好可以用于事件驅(qū)動(dòng)機(jī)制,事件(event)相當(dāng)于被觀察對(duì)象(Subject)挠蛉,一旦事件被觸發(fā),就會(huì)調(diào)用事件處理函數(shù)肄满,可見(jiàn)事件處理函數(shù)(C#中的委托)可以看作是觀察者谴古。因此可以像如下這樣實(shí)現(xiàn)上文中的功能。
/*事件類*/
public Event {
//與事件相關(guān)的事件處理函數(shù)
public ArrayList<Callback> callbackList;
//事件觸發(fā)函數(shù)
public void emit(){
for(Callback cb : callbackList){
cb.run();
}
}
//注冊(cè)事件處理函數(shù)
public registerCallback(Callback cb){
callbackList.add(cb);
}
}
/*事件處理函數(shù)類*/
public interface Callback {
void run();
}
public OnClick implements Callback {
//函數(shù)
public void run(){
System.out.println("you just clicked me!");
}
/*實(shí)現(xiàn)事件驅(qū)動(dòng)*/
Event e = new Event();
//將OnClick事件處理函數(shù)注冊(cè)到事件中
e.registerCallback(new OnClick());
//觸發(fā)事件
e.emit();
上面的Java代碼實(shí)現(xiàn)了一種簡(jiǎn)單的事件驅(qū)動(dòng)機(jī)制稠歉,原理很簡(jiǎn)單掰担,是一種典型的觀察者模式的應(yīng)用案例。
- 利用反射
Java語(yǔ)言提供強(qiáng)大的反射功能怒炸,可以在運(yùn)行時(shí)獲取類的各個(gè)組成部分(比如類名带饱、類成員函數(shù)、類屬性等等)并對(duì)其進(jìn)行操作阅羹。下面使用反射來(lái)實(shí)現(xiàn)簡(jiǎn)單的事件驅(qū)動(dòng)機(jī)制勺疼。
/*事件處理類*/
public class EventHandler {
//事件源
private Object sender;
//事件處理函數(shù)名稱(用于反射)
private String callback;
public EventHandler(Object sender, String callback){
this.sender = sender;
this.callback = callback;
}
//事件觸發(fā)
public void emit(){
Class senderType = this.sender.getClass();
try {
//獲取并調(diào)用事件源sender的事件處理函數(shù)
Method method = senderType.getMethod(this.callback);
method.invoke(this.sender);
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
/*事件源*/
public class Button(){
/*可以在此設(shè)置Button類的相關(guān)屬性,比如名字等*/
private String name;
...
//事件處理函數(shù)
public void onClick(){
System.out.println("you just clicked me!");
}
}
/*實(shí)現(xiàn)事件驅(qū)動(dòng)機(jī)制*/
Button b = new Button();
if(/*收到按鈕點(diǎn)擊信號(hào)*/){
EventHandler e = new EventHandler(b, "onClick");
e.emit();
}
上述代碼展示了利用反射實(shí)現(xiàn)的事件驅(qū)動(dòng)機(jī)制捏鱼,利用反射機(jī)制的好處是其具有強(qiáng)大的擴(kuò)展性执庐,比如我的事件處理函數(shù)中可以引入一個(gè)EventArgs的形參,從而可以讓事件本身帶有參數(shù)导梆,這樣就可以讓事件攜帶更多的信息轨淌,改寫(xiě)后的事件處理函數(shù)如下方的代碼所示:
public class EventArgs {
//參數(shù)
String p1;
Integer p2;
...
}
//onClick事件處理函數(shù)改寫(xiě)
public void onClick(Object sender, EventArgs e){
//參數(shù)e提供更多的信息
System.out.println("Hello, you clicked me! " + e.p1 + e.p2);
}
//觸發(fā)函數(shù)emit改寫(xiě)
public void emit(EventArgs e){
Class senderType = this.sender.getClass();
try {
//獲取并調(diào)用事件源sender的事件處理函數(shù)
Method method = senderType.getMethod(this.callback, this.getClass(), e.getClass());
method.invoke(this.sender, this.sender, e);
} catch (Exception e2) {
e2.printStackTrace();
}
}
是不是似曾相識(shí)迂烁?沒(méi)錯(cuò),和用C#寫(xiě)Winform窗體時(shí)递鹉,Visual studio為你自動(dòng)生成的事件處理函數(shù)(代碼中的onClick函數(shù))幾乎具有完全相同的形式盟步,但此時(shí)我們是用Java實(shí)現(xiàn)的。
當(dāng)然躏结,除了以上提到的兩種方法可以實(shí)現(xiàn)Java的事件驅(qū)動(dòng)機(jī)制以外却盘,還有一些其它的方法,比如可以利用Java的內(nèi)部類來(lái)實(shí)現(xiàn)窜觉,筆者也曾寫(xiě)過(guò)一些示例代碼谷炸,這里就不再冗言了,留待以后再講禀挫。