淺談?dòng)胘ava實(shí)現(xiàn)事件驅(qū)動(dòng)機(jī)制
由于項(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ī)制編寫方法。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ù),這樣就可以讓事件攜帶更多的信息,改寫后的事件處理函數(shù)如下方的代碼所示:
public class EventArgs { //參數(shù) String p1; Integer p2; ... } //onClick事件處理函數(shù)改寫 public void onClick(Object sender, EventArgs e){ //參數(shù)e提供更多的信息 System.out.println("Hello, you clicked me! " + e.p1 + e.p2); } //觸發(fā)函數(shù)emit改寫 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#寫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),筆者也曾寫過(guò)一些示例代碼,這里就不再冗言了,留待以后再講。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
java 讀寫Parquet格式的數(shù)據(jù)的示例代碼
本篇文章主要介紹了java 讀寫Parquet格式的數(shù)據(jù)的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09springboot 緩存@EnableCaching實(shí)例
這篇文章主要介紹了springboot 緩存@EnableCaching實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11Spring?Boot?整合JPA?數(shù)據(jù)模型關(guān)聯(lián)使用操作(一對(duì)一、一對(duì)多、多對(duì)多)
這篇文章主要介紹了Spring?Boot?整合JPA?數(shù)據(jù)模型關(guān)聯(lián)操作(一對(duì)一、一對(duì)多、多對(duì)多),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07Spring 處理 HTTP 請(qǐng)求參數(shù)注解的操作方法
這篇文章主要介紹了Spring 處理 HTTP 請(qǐng)求參數(shù)注解的操作方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友參考下吧2024-04-04java學(xué)習(xí)之JVM運(yùn)行時(shí)常量池理解
這篇文章主要介紹了java學(xué)習(xí)之JVM運(yùn)行時(shí)常量池理解,對(duì)常量池的好處以及基本類型的包裝類常量池等作了簡(jiǎn)要分析,有需要的朋友可以借鑒參考下2021-09-09Java實(shí)現(xiàn)調(diào)用jython執(zhí)行python文件的方法
這篇文章主要介紹了Java實(shí)現(xiàn)調(diào)用jython執(zhí)行python文件的方法,結(jié)合實(shí)例形式分析了Java調(diào)用jython執(zhí)行python文件的常見(jiàn)操作技巧及相關(guān)問(wèn)題解決方法,需要的朋友可以參考下2018-03-03java?stream實(shí)現(xiàn)分組BigDecimal求和以及自定義分組求和
這篇文章主要給大家介紹了關(guān)于java?stream實(shí)現(xiàn)分組BigDecimal求和以及自定義分組求和的相關(guān)資料,Stream是Java8的一大亮點(diǎn),是對(duì)容器對(duì)象功能的增強(qiáng),它專注于對(duì)容器對(duì)象進(jìn)行各種非常便利、高效的聚合操作或者大批量數(shù)據(jù)操作,需要的朋友可以參考下2023-12-12JAVA實(shí)現(xiàn)的簡(jiǎn)單萬(wàn)年歷代碼
這篇文章主要介紹了JAVA實(shí)現(xiàn)的簡(jiǎn)單萬(wàn)年歷代碼,涉及Java日期操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10