觀察者模式
觀察者模式也稱為訂閱者模式,實(shí)際上我覺得訂閱者更容易理解。這種設(shè)計(jì)模式在生活中很常見。比如訂閱期刊雜志、定牛奶等等。我們使用的軟件中也很常見。比如說微博,你關(guān)注了某位明星,其實(shí)你就是他的觀察者。每當(dāng)你關(guān)注的明星發(fā)了新的動(dòng)態(tài),你就會(huì)接收到通知。觀察者模式基于發(fā)布訂閱的方式。訂閱者訂閱目標(biāo)對(duì)象,目標(biāo)對(duì)象維護(hù)訂閱者的集合。一旦目標(biāo)對(duì)象狀態(tài)變化,需要通知所有訂閱者,從而觸發(fā)訂閱者的某個(gè)行為。
1. 實(shí)現(xiàn)觀察者模式
實(shí)現(xiàn)觀察者模式,在目標(biāo)對(duì)象中需要維護(hù)所有他的觀察者引用。觀察者可以觀察多個(gè)不同目標(biāo)對(duì)象的,所以需要讓觀察者知道是哪個(gè)目標(biāo)對(duì)象發(fā)送的通知。下面我們通過一個(gè)簡單的例子來看看如何實(shí)現(xiàn)觀察者模式。
這個(gè)例子叫老師點(diǎn)名了。上大學(xué)時(shí)候,經(jīng)常有同學(xué)曠課在宿舍打游戲,并且囑咐去上課的同學(xué),老師要是點(diǎn)名了給我打電話。還好宿舍離教學(xué)樓近,接到通知的同學(xué)趕緊跑去教室也能趕上。有的膽子大點(diǎn)的同學(xué),接到通知后也不去上課,而是找個(gè)關(guān)系好的同學(xué)幫忙喊聲到。
去上課的同學(xué)是通知者(目標(biāo)對(duì)象),他持有所有需要他通知老師點(diǎn)名的同學(xué)(觀察者)的引用,才能在老師點(diǎn)名的時(shí)候通知到每個(gè)人。程序中我們一般用容器存儲(chǔ)觀察者。當(dāng)通知的時(shí)候循環(huán)調(diào)用所有觀察者暴露出的更新方法。
“老師點(diǎn)名了” 目標(biāo)對(duì)象代碼如下:
public class TeacherRollCallSubject {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer){
observers.add(observer);
}
public void removeObserver(Observer observer){
observers.remove(observer);
}
public void notifyObservers (){
observers.forEach(Observer::update);
}
}
觀察者只需要實(shí)現(xiàn)一個(gè)方法,供通知者做通知的時(shí)候調(diào)用。我們先定義觀察者的接口。
public interface Observer {
void update();
}
我們定義第一類觀察者的實(shí)現(xiàn),他接到通知后,會(huì)馬上去教室。
public class GotoClassObserver implements Observer {
@Override
public void update() {
System.out.println("老師點(diǎn)名了!");
System.out.println("我要馬上趕到教室去!");
}
}
第二類觀察者,接到通知后,會(huì)通知自己的好朋友幫自己答到。
public class AskForHelpObserver implements Observer {
@Override
public void update() {
System.out.println("老師點(diǎn)名了!");
System.out.println("趕緊給XX發(fā)信息,讓他替我答到!");
}
}
客戶端代碼中,分別聲明兩個(gè)不同的觀察者,然后讓這兩個(gè)觀察者都觀察老師點(diǎn)名了目標(biāo)對(duì)象。最后出發(fā)目標(biāo)對(duì)象的通知方法??蛻舳舜a如:
public class Client {
public static void main(String[] args) {
Observer studentOne = new GotoClassObserver();
Observer studentTwo = new AskForHelpObserver();
TeacherRollCallSubject subject = new TeacherRollCallSubject();
subject.addObserver(studentOne);
subject.addObserver(studentTwo);
subject.notifyObservers();
}
}
運(yùn)行后輸出如下:
老師點(diǎn)名了!
我要馬上趕到教室去!
老師點(diǎn)名了!
趕緊給XX發(fā)信息,讓他替我答到!
可以看到每個(gè)觀察者都接到了通知,并且按照自己實(shí)現(xiàn)的響應(yīng)方式作出不同的邏輯處理。第一個(gè)同學(xué)會(huì)趕到教室。第二個(gè)同學(xué)則是給好朋友發(fā)信息,讓其替他答到。
2. 觀察者模式優(yōu)缺點(diǎn)
2.1 優(yōu)點(diǎn)
1、目標(biāo)對(duì)象狀態(tài)的變化,不需要觀察者真的一直觀察。當(dāng)存在大量觀察者時(shí),如果所有的觀察者都去輪詢狀態(tài),那么系統(tǒng)資源的消耗極大。而觀察者模式避免了這種情況;
2、觀察者模式支持廣播,狀態(tài)變化時(shí),目標(biāo)對(duì)象的所有觀察者都會(huì)得到通知;
3、符合開閉原則,目標(biāo)對(duì)象依賴的是觀察者的接口,可以很方便的對(duì)觀察者進(jìn)行擴(kuò)展,而不需要修改已有觀察者。反過來觀察者也是依賴的目標(biāo)接口。
2.2 缺點(diǎn)
1、觀察者模式中,觀察者接口限定了方法簽名。有一定的局限性。
3. 觀察者模式適用場景
1、抽象模型可以分為兩個(gè)部分,一部分行為取決于另外一部分狀態(tài)的變化。并且你想讓這兩部分各自獨(dú)立。每部分都可以獨(dú)自使用和復(fù)用;
2、一個(gè)對(duì)象的變化,需要通知其他對(duì)象,并且有多少對(duì)象需要通知并不清楚
3、你想讓通知與被通知雙方松耦合。
4. 小結(jié)
觀察者模式最大的優(yōu)點(diǎn)就是把目標(biāo)和觀察者解耦,觀察者根據(jù)目標(biāo)對(duì)象狀態(tài)的變化作出響應(yīng)。而目標(biāo)者可以把自己狀態(tài)變化廣播給所有注冊的觀察者。實(shí)際使用中有推/拉兩種模型。
- 在推模型中,目標(biāo)對(duì)象會(huì)把狀態(tài)改變相關(guān)的所有信息推送出去,信息的量有可能會(huì)很大。
- 在拉模型中,目標(biāo)對(duì)象只推送出最核心的信息,比如變化的數(shù)據(jù) id。
觀察者收到消息后再?zèng)Q定如何處理,比如查詢與變化相關(guān)的自己感興趣的數(shù)據(jù)。推模型,目標(biāo)對(duì)象需要知道所有觀察者對(duì)數(shù)據(jù)的需求。而拉模型效率會(huì)比較差,觀察者收到消息后,還需要自己再去獲取改變的內(nèi)容。關(guān)于推拉模型總結(jié)如下: