概述
觀察者模式是一個(gè)
使用率
非常高的模式,
它最常用的地方是 GUI 系統(tǒng)、訂閱——發(fā)布系統(tǒng)蝎宇。這個(gè)模式的一個(gè)重要作用就是
解耦
,
將被觀察者
和觀察者
解耦祷安,
使得它們之間的依賴性更小姥芥,甚至做到毫無(wú)依賴。以GUI系統(tǒng)來(lái)說(shuō)汇鞭,應(yīng)用的UI具有易變性凉唐,
尤其是前期隨著業(yè)務(wù)的改變或者產(chǎn)品的需求修改,
應(yīng)用界面
也會(huì)經(jīng)常性變化
霍骄,但是業(yè)務(wù)邏輯
基本變化不大台囱,
此時(shí),GUI系統(tǒng)需要一套機(jī)制來(lái)應(yīng)對(duì)這種情況读整,
使得UI層
與具體的業(yè)務(wù)邏輯
解耦玄坦,觀察者模式此時(shí)就派上用場(chǎng)了。
;娉痢<彘埂!
因?yàn)?code>觀察者僅負(fù)責(zé)調(diào)度被觀察者
的更新方法
车伞,
并提供一個(gè)業(yè)務(wù)數(shù)據(jù)給被觀察者
择懂;
被觀察者
具體實(shí)現(xiàn)更新方法
【可以實(shí)現(xiàn)UI、數(shù)據(jù)更新】另玖,
其更新方法
具體實(shí)現(xiàn)的內(nèi)容
與觀察者
的業(yè)務(wù)邏輯
基本毫無(wú)依賴困曙!
定義
定義對(duì)象間一種一對(duì)多
的依賴關(guān)系
,
使得每當(dāng)一個(gè)對(duì)象
改變狀態(tài)谦去,
則所有依賴于它的對(duì)象
都會(huì)得到通知
并被自動(dòng)更新
慷丽。
使用場(chǎng)景
●關(guān)聯(lián)行為場(chǎng)景,需要注意的是鳄哭,關(guān)聯(lián)行為是可拆分的要糊,而不是“組合”關(guān)系;
●事件多級(jí)觸發(fā)場(chǎng)景妆丘;
●跨系統(tǒng)的消息交換場(chǎng)景锄俄,如消息隊(duì)列
、事件總線
的處理機(jī)制
勺拣。
UML類圖
●Subject:抽象主題奶赠,也就是被觀察(Observable)的角色,抽象主題角色把所有觀察者對(duì)象的引用保存在一個(gè)集合里药有,每個(gè)主題都可以有任意數(shù)量的觀察者毅戈,抽象主題提供一個(gè)接口,可以增加和刪除觀察者對(duì)象。
●ConcreteSubject:具體主題苇经,該角色將有關(guān)狀態(tài)存入具體觀察者對(duì)象赘理,在具體主題的內(nèi)部狀態(tài)發(fā)生改變時(shí),給所有注冊(cè)過(guò)的觀察者發(fā)出通知塑陵,具體主題角色又叫做具體被觀察者(Concrete Observable)角色感憾。
●Observer:抽象觀察者蜡励,該角色是觀察者的抽象類令花,它定義了一個(gè)更新接口,使得在得到主題的更改通知時(shí)更新自己凉倚。
觀察者模式實(shí)現(xiàn)思路總結(jié)
觀察者接口準(zhǔn)備更新(數(shù)據(jù)或UI的)方法兼都;
被觀察者接口準(zhǔn)備三個(gè)抽象方法;
觀察者實(shí)現(xiàn)類具體實(shí)現(xiàn)更新邏輯稽寒,可以有參數(shù)扮碧,參數(shù)為更新需要的數(shù)據(jù);
被觀察者實(shí)現(xiàn)類準(zhǔn)備一個(gè)觀察者List以及實(shí)現(xiàn)三個(gè)方法:
1.觀察者注冊(cè)方法:
參數(shù)為某觀察者杏糙,功能是把觀察者參數(shù)加到觀察者List中慎王;
2.注銷觀察者方法:
參數(shù)為某觀察者,功能是把觀察者參數(shù)從觀察者List中移除宏侍;
3.通知觀察者方法:無(wú)參數(shù)或者把需要通知的數(shù)據(jù)作為參數(shù)赖淤,
功能是遍歷所有已注冊(cè)的觀察者,
即遍歷 注冊(cè)添加到 觀察者List中的觀察者谅河,逐個(gè)調(diào)用List中所有觀察者的更新方法咱旱;即一次性更新所有已注冊(cè)的觀察者!
使用時(shí)绷耍,
實(shí)例化一個(gè)被觀察者和若干個(gè)觀察者吐限,
將所有觀察者注冊(cè)到被觀察者處,
調(diào)用被觀察者的通知方法褂始,一次性更新所有已注冊(cè)的觀察者诸典!
案例
- 準(zhǔn)備一個(gè)消息隊(duì)列崎苗,
每一個(gè)Client發(fā)送過(guò)來(lái)的消息搂赋,
都會(huì)被加入到隊(duì)列當(dāng)中去,
隊(duì)列中默認(rèn)有一個(gè)子線程益缠,
專門從隊(duì)列中脑奠,死循環(huán),不斷去取數(shù)據(jù)(取出隊(duì)列的隊(duì)頭)幅慌,
取到數(shù)據(jù)就做相關(guān)處理宋欺,比如分發(fā)給其他的socket;
/**
* <pre>
* desc :每一個(gè)Client發(fā)送過(guò)來(lái)的消息,
* 都會(huì)被加入到隊(duì)列當(dāng)中去齿诞,
* 隊(duì)列中默認(rèn)有一個(gè)子線程酸休,
* 專門從隊(duì)列中,死循環(huán)祷杈,不斷去取數(shù)據(jù)斑司,
* 取到數(shù)據(jù)就做相關(guān)處理,比如分發(fā)給其他的socket但汞;
* </pre>
*/
public class MsgPool {
private static MsgPool mInstance = new MsgPool();
/*
這里默認(rèn)消息是String類型宿刮,
或者可以自行封裝一個(gè)Model 類,存儲(chǔ)更詳細(xì)的信息
block n.塊私蕾; 街區(qū)僵缺;障礙物,阻礙
顧名思義踩叭,這是一個(gè)阻塞的隊(duì)列磕潮,當(dāng)有消息過(guò)來(lái)時(shí),就把消息發(fā)送給這個(gè)隊(duì)列容贝,
這邊會(huì)起一個(gè)線程專門從隊(duì)列里面去取消息自脯,
如果隊(duì)列中沒有消息,就會(huì)阻塞在原地
*/
private LinkedBlockingQueue<String> mQueue = new LinkedBlockingQueue<>();
public static MsgPool getInstance() {
return mInstance;
}
private MsgPool() {
}
//這是一個(gè)阻塞的隊(duì)列斤富,
// 當(dāng)有消息過(guò)來(lái)時(shí)膏潮,即客戶端接收到消息時(shí),
// 就把消息發(fā)送(添加)到這個(gè)隊(duì)列中
//現(xiàn)在所有的客戶端都可以發(fā)送消息到這個(gè)隊(duì)列中
public void sendMsg(String msg) {
try {
mQueue.put(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//要一早就調(diào)用本方法茂缚,
// 啟動(dòng)這個(gè)讀取消息的線程戏罢,在后臺(tái)不斷運(yùn)行
public void start() {
//開啟一個(gè)線程去讀隊(duì)列的數(shù)據(jù)
new Thread() {
@Override
public void run() {
//無(wú)限循環(huán)讀取信息
while (true) {
try {
//取出并移除隊(duì)頭;沒有消息時(shí)脚囊,take()是阻塞的
String msg = mQueue.take();
notifyMsgComing(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
//被觀察者方法龟糕,遍歷所有已注冊(cè)的觀察者,一次性通知更新
private void notifyMsgComing(String msg) {
for (MsgComingListener listener : mListeners) {
listener.onMsgComing(msg);
}
}
//觀察者接口
public interface MsgComingListener {
void onMsgComing(String msg);//更新方法
}
//被觀察者悔耘,存放觀察者
private List<MsgComingListener> mListeners = new ArrayList<>();
//被觀察者方法讲岁,添加觀察者到列表
public void addMsgComingListener(MsgComingListener listener) {
mListeners.add(listener);
}
}
案例小結(jié):
所有的客戶端都可發(fā)送消息到隊(duì)列中,
然后所有的客戶端都在等待
消息隊(duì)列的消息新增(mQueue.put())這個(gè)時(shí)刻衬以,
消息隊(duì)列一新增消息缓艳,
即一接收到某個(gè)客戶端發(fā)送過(guò)來(lái)消息(mQueue.put()),
則消息都會(huì)一次性轉(zhuǎn)發(fā)給所有客戶端看峻,
所以這里涉及到一個(gè)觀察者設(shè)計(jì)模式阶淘,
消息隊(duì)列(MsgPool)或消息(Msg)是被觀察者,
所有客戶端處理線程(ClientTask)都是觀察者
參考:
- 《Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)》
- 慕課網(wǎng)