Java 設計模式(八)觀察者模式
阿新 • • 發佈:2019-02-09
一、定義
觀察者模式定義了一個一對多的依賴關係,讓多個觀察者物件同時監聽同一個主題物件。當這個主題狀態發生改變時,會通知所有觀察者物件,讓它們自動更新自己。
二、類似場景
- 聊天室程式的建立。伺服器建立好後,A、B、C三個客戶端連線好公開聊天。A向伺服器傳送資料,伺服器在將資料分別傳送給其他線上客戶。也就是說,每個客戶端需要更新伺服器端的資料。
- 網站上,很多人訂閱了“Java主題”的新聞。當有這個主題新聞時,就會將這些新聞發給所有訂閱的人。
大家在玩CS遊戲時,伺服器需要將每個人的方位變化發給所有的客戶。
上面這些場景,我們都可以使用觀察者模式來處理。我們可以把多個訂閱者、客戶稱之為觀察者
三、模式結構
- 抽象主題角色(Subject): 把所有對觀察者物件的引用儲存在一個集合中,每個抽象主題角色都可以有任意數量的觀察者。抽象主題提供一個介面,可以增加和刪除觀察者角色。一般用一個抽象類和介面來實現。
- 具體主題角色(ConcreteSubject): 在具體主題內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色通常用一個子類實現。
- 抽象觀察者角色(Observer): 為所有具體的觀察者定義一個介面,在得到主題的通知時更新自己。
- 具體觀察者角色(ConcreteObserver): 該角色實現抽象觀察者角色所要求的更新介面,以便使本身的狀態與主題的狀態相協調。通常用一個子類實現。如果需要,具體觀察者角色可以儲存一個指向具體主題角色的引用。
類圖:
程式碼示例:
import java.util.*;
//抽象觀察類
interface Observer {
public String getName();
public void setName(String name);
public void help(); //宣告支援盟友方法
public void beAttacked(AllyControlCenter acc); //宣告遭受攻擊方法
}
//戰隊成員類:具體觀察者類
class Player implements Observer {
private String name;
public Player(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
//支援盟友方法的實現
public void help() {
System.out.println("堅持住," + this.name + "來救你!");
}
//遭受攻擊方法的實現,當遭受攻擊時將呼叫戰隊控制中心類的通知方法notifyObserver()來通知盟友
public void beAttacked(AllyControlCenter acc) {
System.out.println(this.name + "被攻擊!");
acc.notifyObserver(name);
}
}
//戰隊控制中心類:目標類
abstract class AllyControlCenter {
protected String allyName; //戰隊名稱
protected ArrayList<Observer> players = new ArrayList<Observer>(); //定義一個集合用於儲存戰隊成員
public void setAllyName(String allyName) {
this.allyName = allyName;
}
public String getAllyName() {
return this.allyName;
}
//註冊方法
public void join(Observer obs) {
System.out.println(obs.getName() + "加入" + this.allyName + "戰隊!");
players.add(obs);
}
//登出方法
public void quit(Observer obs) {
System.out.println(obs.getName() + "退出" + this.allyName + "戰隊!");
players.remove(obs);
}
//宣告抽象通知方法
public abstract void notifyObserver(String name);
}
//具體戰隊控制中心類:具體目標類
class ConcreteAllyControlCenter extends AllyControlCenter {
public ConcreteAllyControlCenter(String allyName) {
System.out.println(allyName + "戰隊組建成功!");
System.out.println("----------------------------");
this.allyName = allyName;
}
//實現通知方法
public void notifyObserver(String name) {
System.out.println(this.allyName + "戰隊緊急通知,盟友" + name + "遭受敵人攻擊!");
//遍歷觀察者集合,呼叫每一個盟友(自己除外)的支援方法
for(Object obs : players) {
if (!((Observer)obs).getName().equalsIgnoreCase(name)) {
((Observer)obs).help();
}
}
}
}
編寫如下客戶端測試程式碼:
class Client {
public static void main(String args[]) {
//定義觀察目標物件
AllyControlCenter acc;
acc = new ConcreteAllyControlCenter("金庸群俠");
//定義四個觀察者物件
Observer player1,player2,player3,player4;
player1 = new Player("楊過");
acc.join(player1);
player2 = new Player("令狐沖");
acc.join(player2);
player3 = new Player("張無忌");
acc.join(player3);
player4 = new Player("段譽");
acc.join(player4);
//某成員遭受攻擊
Player1.beAttacked(acc);
}
}
編譯並執行程式,輸出結果如下:
金庸群俠戰隊組建成功! —————————- 楊過加入金庸群俠戰隊! 令狐沖加入金庸群俠戰隊! 張無忌加入金庸群俠戰隊! 段譽加入金庸群俠戰隊! 楊過被攻擊! 金庸群俠戰隊緊急通知,盟友楊過遭受敵人攻擊! 堅持住,令狐沖來救你! 堅持住,張無忌來救你! 堅持住,段譽來救你! |
在本例項中,實現了兩次物件之間的聯動,當一個遊戲玩家Player物件的beAttacked()方法被呼叫時,將呼叫AllyControlCenter的notifyObserver()方法來進行處理,而在notifyObserver()方法中又將呼叫其他Player物件的help()方法。Player的beAttacked()方法、AllyControlCenter的notifyObserver()方法以及Player的help()方法構成了一個聯動觸發鏈,執行順序如下所示:
Player.beAttacked() –> AllyControlCenter.notifyObserver() –>Player.help()
四、推模式與拉模式
- 推模式:每次都會把通知以廣播的方式傳送給所有觀察者,所有觀察者只能被動接收, 推送的資訊通常是主題物件的全部或部分資料 。
- 拉模式: 主題物件在通知觀察者的時候,只傳遞少量資訊。如果觀察者需要更具體的資訊,由觀察者主動到主題物件中獲取,相當於是觀察者從主題物件中拉資料。一般這種模型的實現中,會把主題物件自身通過update()方法傳遞給觀察者,這樣在觀察者需要獲取資料的時候,就可以通過這個引用來獲取了 。
- 比較: 推模式是假定主題物件知道觀察者需要的資料;而拉模式是主題物件不知道觀察者具體需要什麼資料,沒有辦法的情況下,乾脆把自身傳遞給觀察者,讓觀察者自己去按需要取值。
五、Java自帶對觀察者模式的支援
JavaSE中提供了java.util.Observable和java.util.Observer來實現觀察者模式。
程式碼示例:
具體目標物件:
public class ConcreteSubject extends Observable{
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;//目標物件狀態發生改變
setChanged();//表示目標物件已經做了更改
notifyObservers(state);//通知所有觀察者
}
}
具體觀察者:
public class ConcreteObserver implements Observer{
private int mystate;
public void update(Observable o, Object arg) {
mystate=((ConcreteSubject)o).getState();
System.out.println("觀察者接收到的狀態:"+mystate);
}
}
客戶端:
public class Client {
public static void main(String[] args) {
//建立具體主題
ConcreteSubject cs=new ConcreteSubject();
//建立觀察者
ConcreteObserver observer1=new ConcreteObserver();
ConcreteObserver observer2=new ConcreteObserver();
ConcreteObserver observer3=new ConcreteObserver();
//將觀察者加入到目標物件觀察者集合
cs.addObserver(observer1);
cs.addObserver(observer2);
cs.addObserver(observer3);
//目標物件改變
cs.setState(100);
}
}
輸出結果: