Java 設計模式 觀察者模式
在閻宏博士的《JAVA與模式》一書中開頭是這樣描述觀察者(Observer)模式的:觀察者模式是對象的行為模式,又叫發布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。
觀察者模式的結構
一個軟件系統通常要求在某個對象的狀態變化時,其他的某些對象隨之變化。觀察者模式是滿足這一要求的所有方案中最好的一種。結構圖如下:
涉及的角色:
抽象主題(Subject)角色:抽象主題角色又叫做抽象被觀察者(Observable)角色。定義一個可以增加和刪除觀察者對象的接口,每個主題把所有觀察者對象的引用保存在一個集合(例如ArrayList)裏面。
具體主題(ConcreteSubject)角色:具體主題角色又叫做具體被觀察者(Concrete Observable)角色。在具體主題的狀態變化時,給所有登記過的觀察者發出通知。
抽象觀察者(Observer)角色:為所有的具體觀察者定義一個更新接口,收到主題的通知時更新具體觀察者。
具體觀察者(ConcreteObserver)角色:存儲與主題狀態適應的狀態,實現了抽象觀察者角色的更新接口。
抽象主題角色類
1 public abstract class Subject {
2 /**
3 * 用來保存註冊的觀察者對象
4 */
5 private List<Observer> list = new ArrayList<Observer>();
6 /**
7 * 註冊觀察者對象
8 * @param observer 觀察者對象
9 */
10 public void attach(Observer observer){
11
12 list.add(observer);
13 System.out.println("Attached an observer");
14 }
15 /**
16 * 刪除觀察者對象
17 * @param observer 觀察者對象
18 */
19 public void detach(Observer observer){
20
21 list.remove(observer);
22 }
23 /**
24 * 通知所有註冊的觀察者對象
25 */
26 public void nodifyObservers(String newState){
27
28 for(Observer observer : list){
29 observer.update(newState);
30 }
31 }
32 }
具體主題角色類
1 public class ConcreteSubject extends Subject{
2
3 private String state;
4
5 public String getState() {
6 return state;
7 }
8
9 public void change(String newState){
10 state = newState;
11 System.out.println("主題狀態為:" + state);
12 //狀態發生改變,通知各個觀察者
13 this.nodifyObservers(state);
14 }
15 }
抽象觀察者角色類
1 public interface Observer {
2 /**
3 * 更新接口
4 * @param state 更新的狀態
5 */
6 public void update(String state);
7 }
具體觀察者角色類
1 public class ConcreteObserver implements Observer {
2 //觀察者的狀態
3 private String observerState;
4
5 @Override
6 public void update(String state) {
7 /**
8 * 更新觀察者的狀態,使其與目標的狀態保持一致
9 */
10 observerState = state;
11 System.out.println("狀態為:"+observerState);
12 }
13
14 }
客戶端類
1 public class Client {
2
3 public static void main(String[] args) {
4 //創建主題對象
5 ConcreteSubject subject = new ConcreteSubject();
6 //創建觀察者對象
7 Observer observer = new ConcreteObserver();
8 //將觀察者對象登記到主題對象上
9 subject.attach(observer);
10 //改變主題對象的狀態
11 subject.change("new state");
12 }
13
14 }
結果:
客戶端先創建具體主題類的實例和一個觀察者對象。然後,它調用主題對象的attach方法,將觀察者對象登記到主題對象上,即把它加入到主題對象的集合中。客戶端調用主題對象的change方法後改變了主題對象的狀態。主題對象在狀態變化時,調用父類的notifyObservers方法,通知所有登記過的觀察者對象,更新它們的狀態。
推模型和拉模型
觀察者模式分為推模型和拉模型兩種方式。
推模型:不管觀察者對象是否需要,主題對象都會向觀察者對象推送主題的詳細信息。
拉模型: 主題對象在通知觀察者對象時,只傳遞少量信息。如果觀察者對象需要更具體的信息,觀察者對象需要主動向主題對象獲取,相當於觀察者對象從主題對象中拉數據。通過update方法把主題對象傳遞給觀察者對象,通過主題對象獲取信息。
前面的例子是推模型,下面給出一個拉模型的實例:
拉模型的抽象觀察者類
1 public interface Observer {
2 /**
3 * 更新接口
4 * @param subject 傳入主題對象,方面獲取相應的主題對象的狀態
5 */
6 public void update(Subject subject);
7 }
拉模型的具體觀察者類
1 public class ConcreteObserver implements Observer {
2 //觀察者的狀態
3 private String observerState;
4
5 @Override
6 public void update(Subject subject) {
7 /**
8 * 更新觀察者的狀態,使其與目標的狀態保持一致
9 */
10 observerState = ((ConcreteSubject)subject).getState();
11 System.out.println("觀察者狀態為:"+observerState);
12 }
13
14 }
拉模型的抽象主題類
1 public abstract class Subject {
2 /**
3 * 用來保存註冊的觀察者對象
4 */
5 private List<Observer> list = new ArrayList<Observer>();
6 /**
7 * 註冊觀察者對象
8 * @param observer 觀察者對象
9 */
10 public void attach(Observer observer){
11
12 list.add(observer);
13 System.out.println("Attached an observer");
14 }
15 /**
16 * 刪除觀察者對象
17 * @param observer 觀察者對象
18 */
19 public void detach(Observer observer){
20
21 list.remove(observer);
22 }
23 /**
24 * 通知所有註冊的觀察者對象
25 */
26 public void nodifyObservers(){
27
28 for(Observer observer : list){
29 observer.update(this);
30 }
31 }
32 }
拉模型的具體主題類
1 public class ConcreteSubject extends Subject{
2
3 private String state;
4
5 public String getState() {
6 return state;
7 }
8
9 public void change(String newState){
10 state = newState;
11 System.out.println("主題狀態為:" + state);
12 //狀態發生改變,通知各個觀察者
13 this.nodifyObservers();
14 }
15 }
兩種模式的比較
推模型是假設主題對象知道觀察者對象需要的數據;而拉模型是主題對象不知道觀察者對象需要什麽數據。
推模型可能會使觀察者對象難以復用,因為觀察者對象的update方法是按需要定義形參,可能無法兼顧沒有考慮到的使用情況。當新情況出現時,就可能需要提供新的update方法,或者重新實現觀察者類;而拉模型不會出現這種情況,因為觀察者對象的update方法的形參是主題對象,通過主題對象可以獲取所有信息。
Java對觀察者模式的支持
java.util庫提供了一個Observer接口和一個Observable類。
Observer接口
只定義了一個update方法。當被觀察者對象的狀態變化時,被觀察者對象的notifyObservers方法就會調用該方法。
1 public interface Observer {
2 void update(Observable o, Object arg);
3 }
Observable類
被觀察者類相當於抽象主題類,是java.util.Observable類的子類。有兩個方法對Observable的子類來說很重要:一個是setChanged方法,被調用後會設置一個標記變量,代表被觀察者對象的狀態變化了。第二個是notifyObservers方法,被調用時會調用所有登記過的觀察者對象的update方法,更新觀察者對象。
1 public class Observable {
2 private boolean changed = false;
3 private Vector obs;
4
5 /** Construct an Observable with zero Observers. */
6
7 public Observable() {
8 obs = new Vector();
9 }
10
11 /**
12 * 將一個觀察者添加到觀察者聚集上面
13 */
14 public synchronized void addObserver(Observer o) {
15 if (o == null)
16 throw new NullPointerException();
17 if (!obs.contains(o)) {
18 obs.addElement(o);
19 }
20 }
21
22 /**
23 * 將一個觀察者從觀察者聚集上刪除
24 */
25 public synchronized void deleteObserver(Observer o) {
26 obs.removeElement(o);
27 }
28
29 public void notifyObservers() {
30 notifyObservers(null);
31 }
32
33 /**
34 * 如果本對象有變化(那時hasChanged 方法會返回true)
35 * 調用本方法通知所有登記的觀察者,即調用它們的update()方法
36 * 傳入this和arg作為參數
37 */
38 public void notifyObservers(Object arg) {
39
40 Object[] arrLocal;
41
42 synchronized (this) {
43
44 if (!changed)
45 return;
46 arrLocal = obs.toArray();
47 clearChanged();
48 }
49
50 for (int i = arrLocal.length-1; i>=0; i--)
51 ((Observer)arrLocal[i]).update(this, arg);
52 }
53
54 /**
55 * 將觀察者聚集清空
56 */
57 public synchronized void deleteObservers() {
58 obs.removeAllElements();
59 }
60
61 /**
62 * 將“已變化”設置為true
63 */
64 protected synchronized void setChanged() {
65 changed = true;
66 }
67
68 /**
69 * 將“已變化”重置為false
70 */
71 protected synchronized void clearChanged() {
72 changed = false;
73 }
74
75 /**
76 * 檢測本對象是否已變化
77 */
78 public synchronized boolean hasChanged() {
79 return changed;
80 }
81
82 /**
83 * Returns the number of observers of this <tt>Observable</tt> object.
84 *
85 * @return the number of observers of this object.
86 */
87 public synchronized int countObservers() {
88 return obs.size();
89 }
90 }
如何使用?
被觀察者類Watched
1 public class Watched extends Observable{
2
3 private String data = "";
4
5 public String getData() {
6 return data;
7 }
8
9 public void setData(String data) {
10
11 if(!this.data.equals(data)){
12 this.data = data;
13 setChanged();
14 }
15 notifyObservers();
16 }
17
18
19 }
觀察者類Watcher
1 public class Watcher implements Observer{
2
3 public Watcher(Observable o){
4 o.addObserver(this);
5 }
6
7 @Override
8 public void update(Observable o, Object arg) {
9
10 System.out.println("狀態發生改變:" + ((Watched)o).getData());
11 }
12
13 }
測試類Test
public class Test {
public static void main(String[] args) {
//創建被觀察者對象
Watched watched = new Watched();
//創建觀察者對象,並將被觀察者對象登記
Observer watcher = new Watcher(watched);
//給被觀察者狀態賦值
watched.setData("start");
watched.setData("run");
watched.setData("stop");
}
}
先創建Watched和Watcher對象。在創建Watcher對象時,將Watched對象作為參數傳入。然後Watched對象調用setData方法,更改Watched對象的狀態。Watched對象通知登記過的Watcher對象,調用它的update方法。
參考資料
《JAVA與模式》之觀察者模式
Java 設計模式 觀察者模式