什么是觀察者模式雹洗?
概念:定義對(duì)象間一種一對(duì)多的依賴關(guān)系,使得每當(dāng)一個(gè)對(duì)象改變狀態(tài),則所有依賴于它的對(duì)象都會(huì)得到通知并被自動(dòng)更新张肾。
說(shuō)白了就是一個(gè)或多個(gè)觀察者同時(shí)可以觀察一個(gè)被觀察者管宵,當(dāng)被觀察者發(fā)生變化截珍,所有的觀察者都會(huì)收到通知,做相應(yīng)的工作箩朴。一個(gè)最簡(jiǎn)單的例子岗喉,多個(gè)警察盯上一個(gè)小偷,在小偷進(jìn)行偷竊行為時(shí)炸庞,警察們收到信號(hào)實(shí)施抓捕钱床。
為什么要用觀察者模式?
當(dāng)我們?cè)谝粋€(gè)模塊中監(jiān)聽另一個(gè)模塊的事件埠居,而又不想讓它們之間有依賴性查牌,就可以選擇觀察者模式事期。
簡(jiǎn)單實(shí)現(xiàn)
例如簡(jiǎn)書公眾號(hào)不時(shí)推送一些優(yōu)秀文章,只要關(guān)注了它就可以收到推送纸颜,這里我們用戶是觀察者兽泣,簡(jiǎn)書公眾號(hào)就是被觀察者。
/**
* 觀察者
*/
public class User implements Observer {
private String name;
public User(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
// 收到事件通知
System.out.println("Hi," + name + ", 推薦一篇優(yōu)秀文章胁孙, 內(nèi)容:" + arg);
}
}
/**
* 被觀察者
*/
public class JianShuSubscription extends Observable {
public void pushArticle(String content){
// 標(biāo)識(shí)狀態(tài)或者內(nèi)容發(fā)生改變
setChanged();
// 通知所有觀察者
notifyObservers(content);
}
}
public class Test {
public static void main(String[] args){
// 新建被觀察者
JianShuSubscription jianShuSubscription = new JianShuSubscription();
// 觀察者
User a = new User("A");
User b = new User("B");
User c = new User("C");
User d = new User("D");
// 將觀察者注冊(cè)到可觀察對(duì)象的觀察者列表中
jianShuSubscription.addObserver(a);
jianShuSubscription.addObserver(b);
// 重復(fù)注冊(cè)
jianShuSubscription.addObserver(b);
jianShuSubscription.addObserver(c);
jianShuSubscription.addObserver(d);
// 發(fā)布消息
jianShuSubscription.pushArticle("觀察者模式");
}
}
運(yùn)行結(jié)果:
可見(jiàn)重復(fù)注冊(cè)也只會(huì)通知一次唠倦,我們來(lái)看看java.util包下的這兩個(gè)類Observer和Observable:
package java.util;
/**
* @author Chris Warth
* @see java.util.Observable
* @since JDK1.0
*/
public interface Observer {
/**
* Observable調(diào)用notifyObservers()方法時(shí)被調(diào)用
* @param o 被觀察的對(duì)象
* @param arg 傳過(guò)來(lái)的信息
*/
void update(Observable o, Object arg);
}
Observer類中只有一個(gè)update方法,在Observable調(diào)用notifyObservers()方法時(shí)被調(diào)用涮较,那我們看一下Observable中的notifyObservers()的方法實(shí)現(xiàn)稠鼻。
package java.util;
public class Observable {
// 是否被改變標(biāo)識(shí)
private boolean changed = false;
// 觀察者集合
private Vector<Observer> obs;
public Observable() {
obs = new Vector<>();
}
/**
* 添加
* @param o
*/
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
// 防止觀察者重復(fù)
if (!obs.contains(o)) {
obs.addElement(o);
}
}
/**
* 刪除
* @param o
*/
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
/**
* 通知所有的觀察者,不攜帶信息
*/
public void notifyObservers() {
notifyObservers(null);
}
/**
* 通知所有的觀察者狂票,攜帶信息arg
*/
public void notifyObservers(Object arg) {
// 臨時(shí)緩沖區(qū)候齿,保存當(dāng)時(shí)的觀察者集合
Object[] arrLocal;
// 這里加鎖顯然是為了線程安全,但是可能會(huì)有2種不希望的結(jié)果
// 1苫亦、最新添加的觀察者無(wú)法接收到這個(gè)通知
// 2毛肋、最近反注冊(cè)掉的觀察者也能接收到這個(gè)通知
synchronized (this) {
// 判斷改變標(biāo)識(shí)
if (!hasChanged())
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
// 逐一調(diào)用觀察者的update()方法
((Observer)arrLocal[i]).update(this, arg);
}
/**
* 清除所有觀察者
*/
public synchronized void deleteObservers() {
obs.removeAllElements();
}
/**
* 把被觀察者標(biāo)識(shí)設(shè)為改變狀態(tài):我準(zhǔn)備發(fā)通知了
*/
protected synchronized void setChanged() {
changed = true;
}
/**
* 我已經(jīng)確認(rèn)發(fā)過(guò)通知了
*/
protected synchronized void clearChanged() {
changed = false;
}
/**
* 判斷此時(shí)是不是正在發(fā)通知
*/
public synchronized boolean hasChanged() {
return changed;
}
/**
* 觀察者數(shù)量
*/
public synchronized int countObservers() {
return obs.size();
}
}
上面注釋已經(jīng)寫得很清楚了,被觀察者發(fā)送通知時(shí)需要調(diào)用2個(gè)方法:
- setChanged() 改變標(biāo)志位屋剑,準(zhǔn)備發(fā)通知润匙。
- notifyObservers(content) 通知所有的觀察者。
缺點(diǎn):
觀察者模式優(yōu)點(diǎn)是:觀察者和被觀察者之間是抽象耦合唉匾,沒(méi)有直接依賴關(guān)系孕讳;增強(qiáng)系統(tǒng)靈活性、可擴(kuò)展性巍膘。
在應(yīng)用觀察者模式時(shí)需要考慮一下開發(fā)效率和運(yùn)行效率問(wèn)題厂财,程序中包括一個(gè)被觀察者、多個(gè)觀察者峡懈,開發(fā)和調(diào)試等內(nèi)容會(huì)比較復(fù)雜璃饱,而且在Java種消息的通知默認(rèn)時(shí)順序執(zhí)行,一個(gè)觀察者卡頓肪康,會(huì)影響整體的運(yùn)行效率荚恶,在這種情況下,要考慮采用異步的方式磷支。