歐陽宏基,楊衛忠,趙 薔
(1.咸陽師范學院信息工程學院,咸陽 712000;2.陜西省高速公路建設集團公司服務區管理分公司,西安 710061)
Erich Gamma 等人在20世紀90年代出版的《Design Patterns:Elements of Reusable Object- Oriented Software》一書中將設計模式的概念從建筑學領域引入到了計算機軟件領域。此書總結了在面向對象軟件開發中所常用的23種設計模式,并將其歸納為三種類型:創建型、行為型和結構型[1]。從軟件領域角度講,設計模式就是以面向對象的軟件實踐過程中所重復出現的、但本質和解決方法十分類似的問題的歸納總結,從思想的高度展示了接口和抽象類在實際案例中的靈活應用[2]。在面向對象的軟件開發中應用設計模式能夠使系統易于維護、擴展和復用。
Observer 模式(觀察者模式)是行為型模式的一種典型代表,該模式的應用場景是:對象之間存在一種一對多的依賴關系,當一個對象的狀態發生變化時,所有依賴它的對象都得到通知并被自動更新狀態或執行相應的操作。Observer 模式在Java JDK 中的典型應用就是異常處理機制和AWT 中的事件處理機制。分析了Observer 模式的各組成部分并將其應用到AWT的事件處理機制中,根據被觀察者與觀察者對象的位置關系,給出了三種具體完成事件處理機制的方案并分析了它們的優缺點。
Observer 模式是關于多個對象想知道一個對象中數據變化情況的一種成熟模式[3]。其中有一個稱作“被觀察者”對象和若干個稱作“觀察者”對象。“被觀察者”與“觀察者”是一對多的依賴關系,當“被觀察者”的狀態發生變化時,所有“觀察者”都得到通知并執行相應的操作。Observer 模式的結構中包括四種角色,它們之間的關系如圖1 所示:
(1)被觀察者接口(Target):該接口定義了具體被觀察者需要實現的方法。例如:添加、刪除觀察者以及通知觀察者更新數據的方法。
(2)觀察者接口(Observer):該接口定義了具體觀察者用來更新數據的方法,當被觀察者發出更新通知時,及時地更新自己,與被觀察者保持一致[4]。
(3)具體被觀察者(ConcreteTarget):具體被觀察者實現了被觀察者接口,該類中包含有可以經常發生變化的數據和一個存放所有觀察者對象引用的集合,當數據發生變化時會通知集合中的每一個觀察者。
(4)具體觀察者(ConcreteObserver):具體觀察者實現了觀察者接口,該類中包含了一個存放具體被觀察者對象的被觀察者接口變量,以便具體觀察者讓具體被觀察者將自己的引用添加到觀察者的集合中,使自己成為它的觀察者。或者讓被觀察者將自己從觀察者集合中刪除,不再擔當觀察者的任務。具體觀察者還要包含當接收到具體被觀察者狀態更新通知后要執行的操作。

圖1 Observer 模式類圖關系
java.awt 包和javax.swing 包提供了利用Java API 創建圖形用戶界面(GUI)的功能。通常觸發一個組件會產生相應的事件(例如點擊界面上的一個Button,會產生一個ActionEvent 事件),事件會被相應的監聽者捕獲并執行相關的操作(例如打開一個新窗口),從而達到與用戶交互的目的,這個過程就是Java的事件處理機制,如圖2 所示。
事件處理機制中包含了三個重要的概念,分別是事件源、事件和監聽器。事件源是產生事件的場所,通常是一些具體的組件,這些組件扮演了Observer 模式中的被觀察者角色。事件是事件源產生的具體對象,充當連接事件源和監聽器的紐帶作用。Java 中定義了許多不同的事件類以描述GUI 程序中可能產生的所有事件,這些事件類都繼承自java.awt.AWTEvent,分為兩大類:低級事件和高級事件[5]。低級事件通常基于組件和容器對象,例如鼠標在一個組件上執行單擊、拖動等動作。高級事件基于語義的,可以不和特定的動作相關聯而依賴于事件源的類型。監聽器是當事件源產生事件后對其進行接收和處理的對象,每一種事件都對應專門的監聽器[6]。通過監聽器使事件的觸發地點和實際處理地點分離,降低了系統內對象的耦合性。監聽器包括監聽接口和監聽接口實現類兩部分。java 根據不同的事件類型定義了不同的監聽接口,監聽接口中定義了若干個針對同一事件所觸發的不同動作的處理方法。監聽接口扮演了Observer 模式中的觀察者接口角色,監聽接口的實現類扮演了Observer模式中的具體觀察者角色,事件源需要調用注冊方法來指定監聽接口實現類的對象作為它的觀察者。

圖2 Java 事件處理機制模型圖
以我院教職工信息管理系統為例,詳細描述Observer 模式在Java 事件處理機制中的應用,給出了三種事件處理方案并比較了它們的優缺點。以點擊系統主窗體所含菜單的某個菜單項,彈出對應的新窗體為情景。菜單項為事件源,當它被點擊后會產生一個ActionEvent 事件(這是一個高級事件),從Observer 模式的角度去理解相當于被觀察者的狀態發生了改變,它會調用notify()方法通知所有注冊的事件監聽器,并將事件的引用傳遞給監聽器。ActionEvent 事件對應的監聽接口為ActionListener。在下面的描述中用被觀察者稱謂代替事件源,觀察者稱謂代替監聽器。
菜單項必須依附于菜單,菜單依附于菜單欄,菜單欄添加在一個窗體中。因此事件源是窗體的屬性,而一般情況下自定義的窗體類都是從Frame 或JFrame 繼承而來,所以窗體類要實現觀察者接口,它的對象作為具體觀察者。核心代碼如下:


此種方式的優點在于不用單獨生成具體觀察者對象,由于被觀察者對象所屬類實現了觀察者接口,因此被觀察者對象在注冊觀察者對象的方法中傳遞this 就可以了。缺點是很可能存在多個同種類型的被觀察者對象,它們會產生相同類型的事件,而且不同類型的被觀察者也有可能產生相同的事件(例如Button 和MenuItem 都會產生ActionEvent 事件),所以觀察者對象在對事件進行操作的代碼中增加了額外的判斷被觀察者對象的邏輯。
此種方式的特點是觀察者與被觀察者在同一個類中,但觀察者類成了被觀察者所在類的內部類,核心代碼如下:

與第一種方式相比,此種方式的優點是被觀察者對象所屬的類不再承擔觀察者的任務,實現了頁面顯示邏輯與監聽邏輯相分離;監聽邏輯中不需要判斷被觀察者對象了。缺點是需要為不同的被觀察者重新定義相對應的觀察者類,可能會出現較多的內部類,被觀察者添加監聽器時創建觀察者對象。
此種方式的特點是觀察者對象所屬類是被觀察者對象所屬類的內部類,只不過這個內部類沒有具體名稱,所以稱為匿名內部類。匿名內部類通常需要繼承一個父類或實現一個接口,核心代碼如下:


與第二種方式相比,此種方式的優點是省去了觀察者作為內部類的命名問題,在被觀察者注冊監聽器的方法中完成匿名內部類的定義與對象的創建。由于沒有引用的存在,這個匿名內部類對象在完成相應的觀察者功能后會被Java的垃圾回收機制直接回收,節省了內存空間。而且這種書寫方式使得代碼看上去簡潔清楚。缺點是匿名內部類的定義與創建對象與普通類還是有明顯區別的,初學者不太容易理解和掌握。
設計模式是設計級的軟件重用方法,通過面向抽象接口編程的方法來降低類間耦合,達到建造具有良好擴展性、健壯性的系統[7]。分析了Observer模式的基本原理并將其應用到Java 事件處理機制的實現中,根據被觀察者與觀察者在同一個類中、觀察者是被觀察者所在類的內部類、觀察者是被觀察者所在類的匿名內部類這三種情況,給出了具體的代碼實現并分析了三者的優缺點。由于監聽器的任務很單一,就是對事件源產生的事件進行處理,所以從命名、節省內存、對象的作用域等幾個方面考慮,采用匿名內部類實現事件處理機制最為合適,優先推薦使用第三種方式。
[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]葛萌,楊衛忠,歐陽宏基.工廠設計模式在Java RMI 中的應用研究[J].計算機與數字工程,2013,41(2):307-307.
[3]耿祥義,張躍平.Java 設計模式[M].北京:清華大學出版社,2009:34-35.
[4]肖力濤,亓常松.基于MVC的Observer 開發模式的擴展及應用[J].計算機與現代化,2012(5):204-205.
[5]邢素萍,王健南.談Java 技術中的事件處理與應用[J].微型電腦應用,2011,27(12):63-63.
[6]杜春濤.Java 6 基礎教程[M].北京:清華大學出版社,2011:288-289.
[7]宋淼,袁兆山,陳剛,劉奎.Java 事件處理機制中設計模式的分析[J].合肥工業大學學報(自然科學版),2004,27(11):1386-1386.