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