寫(xiě)在前
觀察者模式定義:在對(duì)象之間定義一個(gè)一對(duì)多的依賴(lài),一個(gè)主題對(duì)象(Subject)依賴(lài)多個(gè)觀察者對(duì)象(Observer)银锻。當(dāng)主題對(duì)象發(fā)生變化時(shí)永品,會(huì)通知所有觀察者對(duì)象,使它們能夠自動(dòng)更新自己徒仓。
觀察者模式又叫發(fā)布-訂閱模式(Publish/Subscribe)腐碱,有同步阻塞和異步非阻塞這兩種實(shí)現(xiàn)方式。
大白話(huà):其實(shí)就是發(fā)布-訂閱模式掉弛,發(fā)布者發(fā)布信息症见,訂閱者獲取信息,訂閱了就能收到信息殃饿,沒(méi)訂閱就收不到信息谋作。
1.概述
結(jié)構(gòu)圖:
角色:
抽象被觀察者:也就是一個(gè)抽象主題,它把所有對(duì)觀察者對(duì)象的引用保存在一個(gè)集合中乎芳,每個(gè)主題都可以有任意數(shù)量的觀察者遵蚜。抽象主題提供一個(gè)接口,可以增加和刪除觀察者角色奈惑。一般用一個(gè)抽象類(lèi)和接口來(lái)實(shí)現(xiàn)吭净。
抽象觀察者:為所有的具體觀察者定義一個(gè)接口,在得到主題通知時(shí)更新自己肴甸。
具體被觀察者:也就是一個(gè)具體的主題寂殉,在集體主題的內(nèi)部狀態(tài)改變時(shí),所有登記過(guò)的觀察者發(fā)出通知原在。
具體觀察者:實(shí)現(xiàn)抽象觀察者角色所需要的更新接口友扰,一邊使本身的狀態(tài)與制圖的狀態(tài)相協(xié)調(diào)彤叉。
2.場(chǎng)景舉例
有一個(gè)微信公眾號(hào)服務(wù),不定時(shí)發(fā)布一些消息村怪,關(guān)注公眾號(hào)就可以收到推送消息秽浇,取消關(guān)注就收不到推送消息。
/***
* 抽象被觀察者接口
* 聲明了添加甚负、刪除柬焕、通知觀察者方法
*/
public interface Observerable {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObserver();
}
/***
* 抽象觀察者接口
* 定義了一個(gè)update()方法,當(dāng)被觀察者調(diào)用notifyObserver()方法時(shí)腊敲,觀察者的update()方法會(huì)被回調(diào)击喂。
*/
public interface Observer {
public void update(String message);
}
/**
* 具體被觀察者,也就是微信公眾號(hào)服務(wù)
* 實(shí)現(xiàn)了Observerable接口碰辅,對(duì)Observerable接口的三個(gè)方法進(jìn)行了具體實(shí)現(xiàn)
*/
public class WechatServer implements Observerable {
//注意到這個(gè)List集合的泛型參數(shù)為Observer接口懂昂,設(shè)計(jì)原則:面向接口編程而不是面向?qū)崿F(xiàn)編程
private List<Observer> list;
private String message;
public WechatServer() {
list = new ArrayList<Observer>();
}
@Override
public void registerObserver(Observer o) {
list.add(o);
}
@Override
public void removeObserver(Observer o) {
if(!list.isEmpty())
list.remove(o);
}
//list集合保存了注冊(cè)的觀察者,通知觀察者没宾,只需要遍歷集合即可!
@Override
public void notifyObserver() {
for(int i = 0; i < list.size(); i++) {
Observer oserver = list.get(i);
oserver.update(message);
}
}
// 設(shè)置具體主題的內(nèi)部狀態(tài)改變凌彬,并通知觀察者
public void setInfomation(String s) {
this.message = s;
System.out.println("微信服務(wù)更新消息: " + s);
//消息更新,通知所有觀察者
notifyObserver();
}
}
/**
* 具體觀察者循衰,微信公眾號(hào)的具體觀察者為用戶(hù)User
* 實(shí)現(xiàn)了update方法
*/
public class User implements Observer {
private String name;
private String message;
public User(String name) {
this.name = name;
}
@Override
public void update(String message) {
this.message = message;
read();
}
public void read() {
System.out.println(name + " 收到推送消息: " + message);
}
}
/**
編寫(xiě)一個(gè)測(cè)試類(lèi)
首先注冊(cè)了三個(gè)用戶(hù)铲敛,ZhangSan、LiSi会钝、WangWu伐蒋。公眾號(hào)發(fā)布了一條消息"PHP是世界上最好用的語(yǔ)言!"迁酸,三個(gè)用戶(hù)都收到了消息先鱼。
用戶(hù)ZhangSan看到消息后頗為震驚,果斷取消訂閱奸鬓,這時(shí)公眾號(hào)又推送了一條消息焙畔,此時(shí)用戶(hù)ZhangSan已經(jīng)收不到消息,其他用戶(hù)還是正常能收到推送消息串远。
**/
public class Test {
public static void main(String[] args) {
WechatServer server = new WechatServer();
Observer userZhang = new User("ZhangSan");
Observer userLi = new User("LiSi");
Observer userWang = new User("WangWu");
server.registerObserver(userZhang);
server.registerObserver(userLi);
server.registerObserver(userWang);
server.setInfomation("PHP是世界上最好用的語(yǔ)言宏多!");
System.out.println("----------------------------------------------");
// 刪除觀察者ZhangSan(取消訂閱)
server.removeObserver(userZhang);
server.setInfomation("JAVA是世界上最好用的語(yǔ)言!");
}
}
3.總結(jié)與補(bǔ)充
適用情況
對(duì)一個(gè)對(duì)象狀態(tài)的更新澡罚,需要其他對(duì)象同步更新伸但,而且其他對(duì)象的數(shù)量動(dòng)態(tài)可變。
對(duì)象僅需要將自己的更新通知給其他對(duì)象而不需要知道其他對(duì)象的細(xì)節(jié)留搔。
觀察者模式的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
觀察者模式使得被觀察者和觀察者解耦更胖。被觀察者角色所知道的只是一個(gè)具體觀察者列表,每一個(gè)具體觀察者都符合一個(gè)抽象觀察者的接口。被觀察者并不認(rèn)識(shí)任何一個(gè)具體觀察者函喉,它只知道它們都有一個(gè)共同的接口。
觀察者模式支持廣播通訊荣月。被觀察者會(huì)向所有的登記過(guò)的觀察者發(fā)出通知管呵。
缺點(diǎn):
如果一個(gè)被觀察者對(duì)象有很多的直接和間接的觀察者的話(huà),將所有的觀察者都通知到會(huì)花費(fèi)很多時(shí)間哺窄。
如果在被觀察者之間有循環(huán)依賴(lài)的話(huà)捐下,被觀察者會(huì)觸發(fā)它們之間進(jìn)行循環(huán)調(diào)用,導(dǎo)致系統(tǒng)崩潰萌业。在使用觀察者模式是要特別注意這一點(diǎn)坷襟。
如果對(duì)觀察者的通知是通過(guò)另外的線(xiàn)程進(jìn)行異步投遞的話(huà),系統(tǒng)必須保證投遞是以自恰的方式進(jìn)行的生年。
雖然觀察者模式可以隨時(shí)使觀察者知道所觀察的對(duì)象發(fā)生了變化婴程,但是觀察者模式?jīng)]有相應(yīng)的機(jī)制使觀察者知道所觀察的對(duì)象是怎么發(fā)生變化的。
補(bǔ)充場(chǎng)景
JDK中也有自帶的觀察者模式抱婉。但是被觀察者是一個(gè)類(lèi)而不是接口档叔,限制了它的復(fù)用能力。
在JavaBean和Swing中也可以看到觀察者模式的影子蒸绩。