觀察者模式(Observer) 簡介


一, 觀察者模式(Observer) 的定義

觀察者模式:   定義了一種 1對多 的依賴關係, 讓多個觀察者對象同時監聽1個主題對象.

                        這個主題對象在狀態發生變化時, 會通知所有的觀察者對象, 使它們能夠同時更新自己.


稍微解釋一下 這個1 對多 的依賴關係.

 1對多 這個關鍵詞我們常常在DB 表設計裏提到,  但是這裏的意思是有點區別的.

                    

首先,  1 是1個對象, 而不是1個類,     而多也是指多個對象, 而不是多個類.

其次,  這裏的多個對象可以是多個不同的類的對象.  甚至是毫無關係的多個類.


再次, 這個依賴關係, 到底是1個對象依賴多個對象, 還是多個對象依賴1個對象呢.


在這裏的定義來講答案是後者.


但是, 實際上, 1個觀察者也可以觀察多個被觀察者的. (但這就不屬於觀察者模式了)


所以, 觀察者模式(Observer) 也叫做 發佈-訂閱模式(publish/Subscribe).


相當與, 多個讀者同時收聽1個電臺.



二, 觀察者模式(Observer) 的各個角色.


首先睇下Observer 模式的標準UML圖.



我們大概得出上圖有4個角色.


2.1 Observer (觀察者)

Observer是1個接口, 我們可以理解它是1個抽象觀察者類.

它只有1個update()方法, 也就是說它可以通過這個方法來執行某些動作.


2.2 Subject (通知者/被觀察者)

Subject是1個接口, 我們可以理解爲1個抽象被觀察者類.

我們可以見到它有5個方法.


attach() 和 detach()方法用來增刪觀察者的數量, 也就是指當前被觀察者對象到底有多少個觀察者在觀察着它.


setState(), 和 getState() 用於獲取和設置通知者對象本身的狀態, 這個狀態通常是傳送給觀察者們的信息或參數.

也就是說觀察者模式到底在觀察什麼. 無非就是觀察被觀察者的這個狀態.


Notify(),  被觀察者通知所有觀察者, 讓觀察者根據自己的當前狀態(getState())執行自己的update()方法



2.3 ConcreteSubject (具體通知者/被觀察者)

這個(些)就是具體的被觀察者類, 只要實現了Subject接口, 就可以添加1些對象作爲自己的觀察者(或者叫粉絲啦)

寫到這裏, 大家都會瞭解到, 這個類裏面肯定有1個容器, 用於存放觀察者的對象.


這個容器可以根據需要由具體的被觀察者選擇, 通常是無序不能重複的Set容器(例如 HashSet)


而Notify()方法無非就是遍歷自己的觀察者容器, 逐個執行觀察者的update()方法.



2.4 ConcreteObserver (具體觀察者類)

這些類可以是毫無關聯的類, 但是它們都必須實現Observer接口

一旦這些對象被通知者, 加入自己的容器, 就相當於觀察者正在觀察某個被觀察者.


注意,  觀察者可以被多個被觀察者加入自己的容器, 也就是相當於觀察了多個被觀察者了.(但這就break了觀察者模式)




三, 1個具體例子和代碼.


下面我們用1個具體例子來簡單實現這個模式.


我們假定1個事件有3個角色.


1. 指揮者.(Commander)

            指揮炮手打炮, 他可以讓讓若干個炮手和炮灰納入自己的命令範圍.


2. 炮手, (CannonShooter)

          一旦指揮者通知目標, 若干個炮手就往哪個目標轟擊.


3. 炮灰 (CannonFodder)

         一旦指揮者通知, 炮灰就趴下..


也就是說, 炮手必須知道指揮這的狀態(目標信號), 到底打誰.

而炮灰是無序關心到底打哪裏的, 一旦接到通知, 趴下就是了.


3.1 UML圖



3.2 Subject接口 代碼

public interface Subject {
	public void attach(Observer obs);
	public void detach(Observer obs);
	public void sNotify();   //notify is a finel method of Object class
	public int  getState();
	public void setState(int state);
}

5個方法的意義上面已經解釋過了.

通知方法之所以不寫成notify(), 是因爲notify()本身是Object類的1個finel方法



3.2 Observer接口 代碼

public interface Observer {
	public void update();
}

只有1個抽象方法update()



3.3 Commander 類 代碼

import java.util.HashSet;
import java.util.Iterator;
public class Commander implements Subject{
	private int targetPlaceID;
	private HashSet<Observer> gunnerSet = new HashSet<Observer>();

	
	@Override
	public void attach(Observer obs){
		this.gunnerSet.add(obs);
	}

	@Override
	public void detach(Observer obs) {
		this.gunnerSet.remove(obs);
	}

	@Override
	public void sNotify() {
		if (this.gunnerSet.isEmpty()){
			return;
		}
		
		Iterator itr = this.gunnerSet.iterator();
		while (itr.hasNext()){
			Observer obs = (Observer)itr.next();
			obs.update();
		}
	}

	@Override
	public int getState() {
		// TODO Auto-generated method stub
		return this.targetPlaceID;
	}

	@Override
	public void setState(int state) {
		// TODO Auto-generated method stub
		this.targetPlaceID = state;
	}

}

它重寫了接口所有方法.

所謂的notify()方法, 無非就是遍歷自己容器的所有觀察者, 該幹嘛的幹嘛(遍歷調用它們的update())方法


3.4 CannonShooter 類 代碼

public class CannonShooter implements Observer{

	private Subject cmder;
	
	public CannonShooter(Subject cmder){
		this.cmder = cmder;
	}
	
	public Subject getCmder() {
		return cmder;
	}

	public void setCmder(Subject cmder) {
		this.cmder = cmder;
	}
	
	public void fireCannon(int targetPlace){
		System.out.println(this.getClass().getSimpleName() + ": fired on target(id:" 
				+ targetPlace + ") by Cannon");
	}
	
	@Override
	public void update() {
		// TODO Auto-generated method stub
		fireCannon(cmder.getState());
	}
}

可以見到,  炮手必須知道指揮者的狀態信息, 所以它裏面必須有個當前指揮者的對象成員.



3.5 CannonFodder 類 代碼

public class CannonFodder implements Observer{
	private int id;
	public CannonFodder(int id){
		this.id = id;
	}
	
	public void getDown(){
		System.out.println(this.getClass().getSimpleName() +" id:"
				+ this.id + " getDowned");
	}

	@Override
	public void update() {
		// TODO Auto-generated method stub
		this.getDown();
	}
}

炮灰無需關心指揮者的狀態, 裏面只需要重寫自己的update()方法就ok.

3.6 CannonFodder 類 代碼


public class ClientObserver {
	public static void f(){
		Commander cmder = new Commander();
		CannonShooter cster = new CannonShooter(cmder);
		
		CannonFodder cfder1 = new CannonFodder(1);
		CannonFodder cfder2 = new CannonFodder(2);
		CannonFodder cfder3 = new CannonFodder(3);
		
		cmder.setState(107);
		cmder.attach(cster);
		cmder.attach(cfder1);
		cmder.attach(cfder2);
		cmder.attach(cfder3);

		cmder.sNotify();
		
		cmder.setState(108);
		cmder.detach(cfder3);
		
		cmder.sNotify();
		
	}
}


上面的代碼不難看懂.


無非就是實例化1個指揮者, 1個炮手, 3個炮灰

首先, 指揮者通知向107目標打炮,   炮手射了, 3個炮灰趴下了.


後來指揮者想向打擊108目標, 但是覺得第3號炮灰不在攻擊範圍,   所以從自己的觀察者容器裏移除3號炮灰.

這時, 炮手向108號目標打擊,  只有1號2號炮灰聽指揮爬下.


相當靈活.



四, 觀察者模式的特點和應用範圍.


4.1 Observer模式的特點

上面的例子中, 觀察者和被觀察者的耦合性不大.

1個subject可以有任意數目的觀察者Observer,  程序猿令subject發出通知時根本無需知道觀察者是誰, 有多少觀察者存在.

而單個觀察者本身也無需知道到底有幾個其他觀察者同時存在.



4.2 什麼時候應該使用Observer模式

很明顯嘛,  就是當1個對象發生改變的同時需要同時改變其他多個對象時.


而且, 觀察者模式令到耦合的雙方, 依賴與接口(抽象), 而不是依賴於具體(非抽象類), 符合封閉-開放模式.






發佈了288 篇原創文章 · 獲贊 300 · 訪問量 63萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章