歐陽宏基,楊衛(wèi)忠,趙 薔
(1.咸陽師范學(xué)院信息工程學(xué)院,咸陽 712000;2.陜西省高速公路建設(shè)集團(tuán)公司服務(wù)區(qū)管理分公司,西安 710061)
Erich Gamma 等人在20世紀(jì)90年代出版的《Design Patterns:Elements of Reusable Object- Oriented Software》一書中將設(shè)計模式的概念從建筑學(xué)領(lǐng)域引入到了計算機(jī)軟件領(lǐng)域。此書總結(jié)了在面向?qū)ο筌浖_發(fā)中所常用的23種設(shè)計模式,并將其歸納為三種類型:創(chuàng)建型、行為型和結(jié)構(gòu)型[1]。從軟件領(lǐng)域角度講,設(shè)計模式就是以面向?qū)ο蟮能浖?shí)踐過程中所重復(fù)出現(xiàn)的、但本質(zhì)和解決方法十分類似的問題的歸納總結(jié),從思想的高度展示了接口和抽象類在實(shí)際案例中的靈活應(yīng)用[2]。在面向?qū)ο蟮能浖_發(fā)中應(yīng)用設(shè)計模式能夠使系統(tǒng)易于維護(hù)、擴(kuò)展和復(fù)用。
Observer 模式(觀察者模式)是行為型模式的一種典型代表,該模式的應(yīng)用場景是:對象之間存在一種一對多的依賴關(guān)系,當(dāng)一個對象的狀態(tài)發(fā)生變化時,所有依賴它的對象都得到通知并被自動更新狀態(tài)或執(zhí)行相應(yīng)的操作。Observer 模式在Java JDK 中的典型應(yīng)用就是異常處理機(jī)制和AWT 中的事件處理機(jī)制。分析了Observer 模式的各組成部分并將其應(yīng)用到AWT的事件處理機(jī)制中,根據(jù)被觀察者與觀察者對象的位置關(guān)系,給出了三種具體完成事件處理機(jī)制的方案并分析了它們的優(yōu)缺點(diǎn)。
Observer 模式是關(guān)于多個對象想知道一個對象中數(shù)據(jù)變化情況的一種成熟模式[3]。其中有一個稱作“被觀察者”對象和若干個稱作“觀察者”對象?!氨挥^察者”與“觀察者”是一對多的依賴關(guān)系,當(dāng)“被觀察者”的狀態(tài)發(fā)生變化時,所有“觀察者”都得到通知并執(zhí)行相應(yīng)的操作。Observer 模式的結(jié)構(gòu)中包括四種角色,它們之間的關(guān)系如圖1 所示:
(1)被觀察者接口(Target):該接口定義了具體被觀察者需要實(shí)現(xiàn)的方法。例如:添加、刪除觀察者以及通知觀察者更新數(shù)據(jù)的方法。
(2)觀察者接口(Observer):該接口定義了具體觀察者用來更新數(shù)據(jù)的方法,當(dāng)被觀察者發(fā)出更新通知時,及時地更新自己,與被觀察者保持一致[4]。
(3)具體被觀察者(ConcreteTarget):具體被觀察者實(shí)現(xiàn)了被觀察者接口,該類中包含有可以經(jīng)常發(fā)生變化的數(shù)據(jù)和一個存放所有觀察者對象引用的集合,當(dāng)數(shù)據(jù)發(fā)生變化時會通知集合中的每一個觀察者。
(4)具體觀察者(ConcreteObserver):具體觀察者實(shí)現(xiàn)了觀察者接口,該類中包含了一個存放具體被觀察者對象的被觀察者接口變量,以便具體觀察者讓具體被觀察者將自己的引用添加到觀察者的集合中,使自己成為它的觀察者?;蛘咦尡挥^察者將自己從觀察者集合中刪除,不再擔(dān)當(dāng)觀察者的任務(wù)。具體觀察者還要包含當(dāng)接收到具體被觀察者狀態(tài)更新通知后要執(zhí)行的操作。
圖1 Observer 模式類圖關(guān)系
java.awt 包和javax.swing 包提供了利用Java API 創(chuàng)建圖形用戶界面(GUI)的功能。通常觸發(fā)一個組件會產(chǎn)生相應(yīng)的事件(例如點(diǎn)擊界面上的一個Button,會產(chǎn)生一個ActionEvent 事件),事件會被相應(yīng)的監(jiān)聽者捕獲并執(zhí)行相關(guān)的操作(例如打開一個新窗口),從而達(dá)到與用戶交互的目的,這個過程就是Java的事件處理機(jī)制,如圖2 所示。
事件處理機(jī)制中包含了三個重要的概念,分別是事件源、事件和監(jiān)聽器。事件源是產(chǎn)生事件的場所,通常是一些具體的組件,這些組件扮演了Observer 模式中的被觀察者角色。事件是事件源產(chǎn)生的具體對象,充當(dāng)連接事件源和監(jiān)聽器的紐帶作用。Java 中定義了許多不同的事件類以描述GUI 程序中可能產(chǎn)生的所有事件,這些事件類都繼承自java.awt.AWTEvent,分為兩大類:低級事件和高級事件[5]。低級事件通?;诮M件和容器對象,例如鼠標(biāo)在一個組件上執(zhí)行單擊、拖動等動作。高級事件基于語義的,可以不和特定的動作相關(guān)聯(lián)而依賴于事件源的類型。監(jiān)聽器是當(dāng)事件源產(chǎn)生事件后對其進(jìn)行接收和處理的對象,每一種事件都對應(yīng)專門的監(jiān)聽器[6]。通過監(jiān)聽器使事件的觸發(fā)地點(diǎn)和實(shí)際處理地點(diǎn)分離,降低了系統(tǒng)內(nèi)對象的耦合性。監(jiān)聽器包括監(jiān)聽接口和監(jiān)聽接口實(shí)現(xiàn)類兩部分。java 根據(jù)不同的事件類型定義了不同的監(jiān)聽接口,監(jiān)聽接口中定義了若干個針對同一事件所觸發(fā)的不同動作的處理方法。監(jiān)聽接口扮演了Observer 模式中的觀察者接口角色,監(jiān)聽接口的實(shí)現(xiàn)類扮演了Observer模式中的具體觀察者角色,事件源需要調(diào)用注冊方法來指定監(jiān)聽接口實(shí)現(xiàn)類的對象作為它的觀察者。
圖2 Java 事件處理機(jī)制模型圖
以我院教職工信息管理系統(tǒng)為例,詳細(xì)描述Observer 模式在Java 事件處理機(jī)制中的應(yīng)用,給出了三種事件處理方案并比較了它們的優(yōu)缺點(diǎn)。以點(diǎn)擊系統(tǒng)主窗體所含菜單的某個菜單項(xiàng),彈出對應(yīng)的新窗體為情景。菜單項(xiàng)為事件源,當(dāng)它被點(diǎn)擊后會產(chǎn)生一個ActionEvent 事件(這是一個高級事件),從Observer 模式的角度去理解相當(dāng)于被觀察者的狀態(tài)發(fā)生了改變,它會調(diào)用notify()方法通知所有注冊的事件監(jiān)聽器,并將事件的引用傳遞給監(jiān)聽器。ActionEvent 事件對應(yīng)的監(jiān)聽接口為ActionListener。在下面的描述中用被觀察者稱謂代替事件源,觀察者稱謂代替監(jiān)聽器。
菜單項(xiàng)必須依附于菜單,菜單依附于菜單欄,菜單欄添加在一個窗體中。因此事件源是窗體的屬性,而一般情況下自定義的窗體類都是從Frame 或JFrame 繼承而來,所以窗體類要實(shí)現(xiàn)觀察者接口,它的對象作為具體觀察者。核心代碼如下:
此種方式的優(yōu)點(diǎn)在于不用單獨(dú)生成具體觀察者對象,由于被觀察者對象所屬類實(shí)現(xiàn)了觀察者接口,因此被觀察者對象在注冊觀察者對象的方法中傳遞this 就可以了。缺點(diǎn)是很可能存在多個同種類型的被觀察者對象,它們會產(chǎn)生相同類型的事件,而且不同類型的被觀察者也有可能產(chǎn)生相同的事件(例如Button 和MenuItem 都會產(chǎn)生ActionEvent 事件),所以觀察者對象在對事件進(jìn)行操作的代碼中增加了額外的判斷被觀察者對象的邏輯。
此種方式的特點(diǎn)是觀察者與被觀察者在同一個類中,但觀察者類成了被觀察者所在類的內(nèi)部類,核心代碼如下:
與第一種方式相比,此種方式的優(yōu)點(diǎn)是被觀察者對象所屬的類不再承擔(dān)觀察者的任務(wù),實(shí)現(xiàn)了頁面顯示邏輯與監(jiān)聽邏輯相分離;監(jiān)聽邏輯中不需要判斷被觀察者對象了。缺點(diǎn)是需要為不同的被觀察者重新定義相對應(yīng)的觀察者類,可能會出現(xiàn)較多的內(nèi)部類,被觀察者添加監(jiān)聽器時創(chuàng)建觀察者對象。
此種方式的特點(diǎn)是觀察者對象所屬類是被觀察者對象所屬類的內(nèi)部類,只不過這個內(nèi)部類沒有具體名稱,所以稱為匿名內(nèi)部類。匿名內(nèi)部類通常需要繼承一個父類或?qū)崿F(xiàn)一個接口,核心代碼如下:
與第二種方式相比,此種方式的優(yōu)點(diǎn)是省去了觀察者作為內(nèi)部類的命名問題,在被觀察者注冊監(jiān)聽器的方法中完成匿名內(nèi)部類的定義與對象的創(chuàng)建。由于沒有引用的存在,這個匿名內(nèi)部類對象在完成相應(yīng)的觀察者功能后會被Java的垃圾回收機(jī)制直接回收,節(jié)省了內(nèi)存空間。而且這種書寫方式使得代碼看上去簡潔清楚。缺點(diǎn)是匿名內(nèi)部類的定義與創(chuàng)建對象與普通類還是有明顯區(qū)別的,初學(xué)者不太容易理解和掌握。
設(shè)計模式是設(shè)計級的軟件重用方法,通過面向抽象接口編程的方法來降低類間耦合,達(dá)到建造具有良好擴(kuò)展性、健壯性的系統(tǒng)[7]。分析了Observer模式的基本原理并將其應(yīng)用到Java 事件處理機(jī)制的實(shí)現(xiàn)中,根據(jù)被觀察者與觀察者在同一個類中、觀察者是被觀察者所在類的內(nèi)部類、觀察者是被觀察者所在類的匿名內(nèi)部類這三種情況,給出了具體的代碼實(shí)現(xiàn)并分析了三者的優(yōu)缺點(diǎn)。由于監(jiān)聽器的任務(wù)很單一,就是對事件源產(chǎn)生的事件進(jìn)行處理,所以從命名、節(jié)省內(nèi)存、對象的作用域等幾個方面考慮,采用匿名內(nèi)部類實(shí)現(xiàn)事件處理機(jī)制最為合適,優(yōu)先推薦使用第三種方式。
[1]Erich Gamma,Richard Helm,Ralph Johnson,John Vlissides.Design Patterns:Elements of Reusable Objected-Oriented Software[M].Reading,MA:Addison-Wesley,1994:2-20.
[2]葛萌,楊衛(wèi)忠,歐陽宏基.工廠設(shè)計模式在Java RMI 中的應(yīng)用研究[J].計算機(jī)與數(shù)字工程,2013,41(2):307-307.
[3]耿祥義,張躍平.Java 設(shè)計模式[M].北京:清華大學(xué)出版社,2009:34-35.
[4]肖力濤,亓常松.基于MVC的Observer 開發(fā)模式的擴(kuò)展及應(yīng)用[J].計算機(jī)與現(xiàn)代化,2012(5):204-205.
[5]邢素萍,王健南.談Java 技術(shù)中的事件處理與應(yīng)用[J].微型電腦應(yīng)用,2011,27(12):63-63.
[6]杜春濤.Java 6 基礎(chǔ)教程[M].北京:清華大學(xué)出版社,2011:288-289.
[7]宋淼,袁兆山,陳剛,劉奎.Java 事件處理機(jī)制中設(shè)計模式的分析[J].合肥工業(yè)大學(xué)學(xué)報(自然科學(xué)版),2004,27(11):1386-1386.