觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知所有觀察者對象,讓他們能夠自動更新自己。今天我們通過模擬按鈕的處理事件來深入Java觀察者模式的學習。
目錄導航
觀察者模式所涉及的角色有:
● 抽象主題(Subject)角色:抽象主題角色把所有對觀察者對象的引用保存在一個聚集(比如ArrayList對象)裡,每個主題都可以有任何數量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象,抽象主題角色又叫做抽象被觀察者(Observable)角色。
● 具體主題(ConcreteSubject)角色:將有關狀態存入具體觀察者對象;在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色又叫做具體被觀察者(Concrete Observable)角色。
● 抽象觀察者(Observer)角色:為所有的具體觀察者定義一個接口,在得到主題的通知時更新自己,這個接口叫做更新接口。
● 具體觀察者(ConcreteObserver)角色:存儲與主題的狀態自恰的狀態。具體觀察者角色實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與主題的狀態 像協調。如果需要,具體觀察者角色可以保持一個指向具體主題對象的引用。
我們建立一個項目去開始今天的測試。項目結構如下:
demo很簡單,點擊button按鈕,修改button上顯示的文字
package com.huhx.frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; /** * writer: huhx */ public class Jframe extends JFrame { private JButton button; public Jframe() { button = new JButton("text"); add(button); setVisible(true); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { button.setText("world"); } }); } public static void main(String[] args) { new Jframe(); } }
java中的內置觀察者模式有兩個類:一個是observable被觀察者類,一個是observer觀察者接口。大體上與自定義模式相同,我們編寫程序時只需繼承obervable類,具體實現observer接口就可以了。值得注意的是:調用notifyObservers()方法之前,必須先調用setChanged()方法。這是因為observable類中把 changed變量初始化為false,notifyObservers()方法會先檢查該變量是否為true,如果不為ture,就不會調用 update()方法,這就要我們調用notifyObservers()之前,先用setChanged()方法將changed變量設置為true。
現在我們以上述例子為引子,使用Java中內置的Observer自己來模擬一個按鈕監聽事件的案例:
一、 創建一個繼承obervable類的被觀察者,在案例中也就是Button,類記為:MyButton
package com.huhx.observer; import java.util.Observable; import java.util.Observer; /** * @author huhx */ public class MyButton extends Observable { public void addListeners(Observer observer) { addObserver(observer); } public void process(Object args) { setChanged(); notifyObservers(args); } public void process() { process(null); } }
二、 創建幾個觀察這個按鈕的觀察者,其實就是按鈕的監聽事件:
模擬點擊事件:
package com.huhx.observer; import java.util.Observable; import java.util.Observer; public class ClickObserver implements Observer { @Override public void update(Observable o, Object arg) { if (arg != null) { System.out.println("click: " + arg.toString()); } else { System.out.println("click no arg"); } } }
模擬按下事件:
package com.huhx.observer; import java.util.Observable; import java.util.Observer; public class PressObserver implements Observer { @Override public void update(Observable o, Object arg) { if (arg != null) { System.out.println("press: " + arg.toString()); } else { System.out.println("press no arg"); } } }
三、 然後我們寫一個測試的類,用於模擬按鈕的事件:這裡的process方法模擬真實按鈕的觸發事件,比如我們按下按鈕。
package com.huhx.observer; import java.util.Observable; import java.util.Observer; public class PersonTest { public static void main(String[] args) { MyButton button = new MyButton(); button.addObserver(new ClickObserver()); button.addListeners(new Observer() { @Override public void update(Observable o, Object arg) { System.out.println("observer"); } }); button.process("huhx"); } }
五、 它的輸出結果如下:
observer click: huhx
上述我們通過案例,來模擬了按鈕的監聽事件原理,現在我們通過源碼來詳細的分析它的運行過程:
一、 Observer是一個觀察者可以實現的接口,它的文檔說明如下:
A class can implement the Observer interface when it wants to be informed of changes in observable objects.
Observer中有一個update()方法:
void update(Observable o, Object arg); // Observable 是觀察者 arg 是notifyObservers方法傳入的參數
二、 Observable是一個被觀察者繼承的類,它的文檔說明大致如下:
This class represents an observable object, or "data" in the model-view paradigm. It can be subclassed to represent an object that the application wants to have observed.
Observable有幾個重要的方法,我們簡單的介紹一下:
void addObserver(Observer o) // 加入觀察者到Vetor中 protected void clearChanged() // 設置change為false int countObservers() // 返回Vetor中觀察者的數量 void deleteObserver(Observer o) // 從Vetor中刪除指定的觀察者 void deleteObservers() // 刪除所有的觀察者 void notifyObservers() // 通知Vetor中的觀察者 void notifyObservers(Object arg) // 通知Vetor中的觀察者,帶參數 boolean hasChanged() // 判斷是否發生改變 protected void setChanged() // 設置changed為true
三、 接下來,我們結合上述的例子來分析Observer的原理:
當執行MyButton button = new MyButton(),由於MyButton是繼承Observable,所以下述代碼會執行。
private boolean changed = false; private Vector obs; public Observable() { obs = new Vector(); }
當執行button.addObserver(new ClickObserver()),把參數中的Observer存放在上述初始化的Vetor中(為了適應多線程,用的是Vetor)
public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } }
當執行button.process("huhx"),如果被觀察者發生了改變,就通知Vetor中的觀察者去執行update方法。
public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); clearChanged(); } for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); }
上述案例中,我們還無法做到對觀察者的過濾通知,現在我們自己實現一個Observer,來對事件監聽的事件過濾,更好的模擬按鈕的事件機制。
一、 定義一個常量類,用於各種事件的標記:
package com.huhx.myobserver; /** * huhx 陳慧 */ public class Event { // 觸發所有的Listener public static final int ALL = -1; // 觸發內部定義的Listener public static final int DEFAULT = 0; public static final int CLICK = 1; public static final int PRESS = 2; public static final int DOWN = 3; public static final int FOCUE = 4; }
二、 定義一個Listener接口,和Java內置的Observer作用類似,注意方法中的參數是MyObservable
package com.huhx.listener; import com.huhx.myobserver.MyObservable; public interface Listener { void actionPerformed(MyObservable o, Object arg); }
三、 定義幾個實現Listener的觀察者,也就是事件:
模擬點擊事件:
package com.huhx.listener; import com.huhx.myobserver.MyObservable; /** * 陳慧 * @author Linux * */ public class ClickListener implements Listener { @Override public void actionPerformed(MyObservable o, Object arg) { if (arg != null) { System.out.println("click: " + arg); } else { System.out.println("click"); } } }
模擬按下事件:
package com.huhx.listener; import com.huhx.myobserver.MyObservable; public class PressListener implements Listener { @Override public void actionPerformed(MyObservable o, Object arg) { System.out.println("press"); } }
模擬定義在內部類中的事件:
package com.huhx.listener; import com.huhx.myobserver.MyObservable; public class DefaultListener implements Listener { @Override public void actionPerformed(MyObservable o, Object arg) { } }
四、 定義一個類似於Observable類的自定義被觀察者:
package com.huhx.myobserver; import java.util.Vector; import com.huhx.listener.ClickListener; import com.huhx.listener.Listener; import com.huhx.listener.PressListener; /** * * @author huhx * */ public class MyObservable { private boolean changed = false; private Vector<Listener> obs; Object[] arrLocal; public MyObservable() { obs = new Vector<>(); } protected synchronized void clearChanged() { changed = false; } public void notifyObserverByEvent(int event, Object args) { synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); clearChanged(); } notifyFixObserver(event, args); } private void notifyFixObserver(int event, Object args) { Listener listener = null; for (int i = arrLocal.length - 1; i >= 0; i--) { listener = (Listener) arrLocal[i]; switch (event) { case Event.PRESS: if (listener instanceof PressListener) ((PressListener) listener).actionPerformed(this, args); break; case Event.CLICK: if (listener instanceof ClickListener) ((ClickListener) listener).actionPerformed(this, args); break; case Event.DEFAULT: if (listener instanceof Listener) listener.actionPerformed(this, args); break; case Event.ALL: listener.actionPerformed(this, args); break; default: break; } } } public void notifyObserverByEvent(int event) { notifyObserverByEvent(event, null); } public void notifyObserverByEvent(Object args) { notifyObserverByEvent(Event.ALL, args); } public synchronized void addListener(Listener listener) { if (listener == null) throw new NullPointerException(); if (!obs.contains(listener)) { obs.addElement(listener); } } protected synchronized void setChanged() { changed = true; } }
五、 為了簡化Button的使用,我們仿造Java中的做法,先創建一個AbstractButton,用於處理共公的代碼:
package com.huhx.myobserver; import com.huhx.listener.Listener; public class AbstractButton extends MyObservable { public void addListeners(Listener listener) { addListener(listener); } public void process(int source, Object args) { setChanged(); switch (source) { case Event.CLICK: notifyObserverByEvent(Event.CLICK, args); break; case Event.PRESS: notifyObserverByEvent(Event.PRESS, args); break; case Event.DEFAULT: notifyObserverByEvent(Event.DEFAULT, args); break; case Event.ALL: notifyObserverByEvent(Event.ALL, args); default: break; } } public void process(int source) { process(source, null); } }
六、 創建一個被觀察者,也就是我們的按鈕Button
package com.huhx.myobserver; /** * * @author Linux * */ public class MyButtton extends AbstractButton { }
七、 在測試中使用:
package com.huhx.myobserver; import com.huhx.listener.ClickListener; import com.huhx.listener.Listener; public class PersonTest { public static void main(String[] args) { MyButtton buttton = new MyButtton(); buttton.addListener(new Listener() { @Override public void actionPerformed(MyObservable observable, Object object) { System.out.println("listener"); System.out.println(observable + ", object: " + object.toString()); } }); buttton.addListener(new ClickListener()); buttton.process(Event.CLICK); // buttton.process(Event.DEFAULT, "world"); } }
它的輸出結果如下:
click: 陳慧