觀察者模式
觀察者模式又稱為訂閱—發布模式,在此模式中,一個目標對象管理所有相依於它的觀察者對象,並且在它本身的狀態改變時主動發出通知。這通常透過呼叫各觀察者所提供的方法來實現。此種模式通常被用來事件處理系統。。
基於事件驅動機制的系統或語言,比如node.js、nio等,不難發現其最終的基礎模式就是觀察者模式,只是不同的應用場景,也會有各自不同的側重。
觀察者
class Watcher implements java.util.Observer { public void update(java.util.Observable obj, Object arg) { System.out.println("Update() called, count is " + ((Integer) arg).intValue()); } }
被觀察者
class BeingWatched extends java.util.Observable { void counter(int period) { for(; period>=0; period-- ) { setChanged(); notifyObservers(new Integer(period)); try { Thread.sleep(100); } catch( InterruptedException e) { System.out.println("Sleep interrupeted" ); } } } }
測試
public class ObserverDemo { public static void main(String[] args) { BeingWatched beingWatched = new BeingWatched();//受查者 Watcher watcher = new Watcher();//觀察者 beingWatched.addObserver(watcher); beingWatched.counter(10); } }
監聽器模式
事件源經過事件的封裝傳給監聽器,當事件源觸發事件后,監聽器接收到事件對象可以回調事件的方法
1、首要定義事件源對象(事件源相當於單擊按鈕事件當中的按鈕對象、屬於被監聽者):
public class DemoSource { private Vector repository = new Vector();//監聽自己的監聽器隊列 public DemoSource(){} public void addDemoListener(DemoListener dl) { repository.addElement(dl); } public void notifyDemoEvent() {//通知所有的監聽器 Enumeration enum = repository.elements(); while(enum.hasMoreElements()) { DemoListener dl = (DemoListener)enum.nextElement(); dl.handleEvent(new DemoEvent(this)); } } }
2、其次定義事件(狀態)對象(該事件對象包裝了事件源對象、作為參數傳遞給監聽器、很薄的一層包裝類):
public class DemoEvent extends java.util.EventObject { public DemoEvent(Object source) { /** * source—事件源對象—如在界面上發生的點擊按鈕事件中的按鈕 所有 Event 在構造時都引用了對象 "source",在邏輯上認為該對象是最初發生有關 Event 的對象 */ super(source); } public void say() { System.out.println("This is say method..."); } }
3、最后定義我們的事件偵聽器接口如下
public interface DemoListener extends java.util.EventListener { /** * EventListener是所有事件偵聽器接口必須擴展的標記接口、因為它是無內容的標記接口、 * * 所以事件處理方法由我們自己聲明如下: * @param dm */ public void handleEvent(DemoEvent dm); }
監聽器實現類
public class DemoListener1 implements DemoListener { public void handleEvent(DemoEvent de) { System.out.println("Inside listener1..."); de.say();//回調 } }
4、測試代碼
public class TestDemo { DemoSource ds; public TestDemo(){ try{ ds = new DemoSource(); //將監聽器在事件源對象中登記: DemoListener1 listener1 = new DemoListener1(); ds.addDemoListener(listener1); ds.addDemoListener(new DemoListener() { public void handleEvent(DemoEvent event) { System.out.println("Method come from 匿名類..."); } }); ds.notifyDemoEvent();//觸發事件、通知監聽器 }catch(Exception ex){ ex.printStackTrace(); } } public static void main(String args[]) { new TestDemo(); } }
總結
1、Observer的實現相對簡單,event-listener需要實現三個角色,observer-observable需要實現兩個角色。
2、Observable的api已經把對觀察者的注冊,刪除等定義好了,而且是線程安全的。而event-listener需要使用者自己實現。
3、兩者都需要自己定義並實現觸發事件的通知。但Observable需要注意要在通知Observer之前調用jdk提供的setChanged()。
4、event-listener是傳統的c/s界面事件模型,分事件源和事件(狀態)角色,事件源要經過事件的包裝、成為事件的屬性之一再傳遞給事件監聽/處理者,這個事件監聽者就相當於觀察者。Observer更簡潔一些。兩者在思想上是統一的,很多框架仍然使用了event-listener模式,比如spring框架的ApplicationEvent,ApplicationListener。
5、監聽器模式是觀察者模式的另一種形態,同樣基於事件驅動模型。監聽器模式更加靈活,可以對不同事件作出相應。但也是付出了系統的復雜性作為代價的,因為我們要為每一個事件源定制一個監聽器以及事件,這會增加系統的負擔。
發布訂閱
觀察者模式實現發布訂閱,可以添加一個調度中心,降低publisher和subscriber的耦合,具體實現中,有兩個版本。
1) 拉模式:目標角色在發生變化后,僅僅告訴觀察者角色“我變化了”;觀察者角色如果想要知道具體的變化細節,則就要自己從目標角色的接口中得到。拉模式是想要就主動表白獲取。
2) 推模式:通知你發生變化的同時,通過一個參數將變化的細節傳遞到觀察者角色中去。推模式是管你要不要,先給你啦。
這兩種模式的使用,取決於系統設計時的需要。如果目標角色比較復雜,並且觀察者角色進行更新時必須得到一些具體變化的信息,則“推模式”比較合適。如果目標角色比較簡單,則“拉模式”就很合適啦。
事件和消息的區別
事件本身即是具有特定業務含義的一種固定結構對象,而消息是數據傳輸過程中的載體。概念上寬泛來講,事件可以稱作是一種消息,而消息不能代替事件。事件反映的是特定的業務狀態,比如訂單創建、服務調用失敗、應用宕機等。
一個事件對象描述的是誰在什么時間做了什么事情,看到這個對象,我們就能知道是發生了什么特定的事情。但是事件對象本身不承載數據傳遞的職能。
消息中間件實現的是消息的存儲,解決的是解耦上下游業務系統。事件處理系統是更多的側重對事件的分析處理,並驅動業務的進一步扭轉。