1、什么是觀(guān)察者模式蔚万?
? 觀(guān)察者模式是一種關(guān)注對(duì)象之間責(zé)任分配的行為模式。觀(guān)察者模式定義了對(duì)象之間的一對(duì)多依賴(lài)關(guān)系临庇,這樣當(dāng)一個(gè)對(duì)象更改狀態(tài)時(shí)反璃,它的所有依賴(lài)關(guān)系都會(huì)被通知并自動(dòng)更新。此模式中的關(guān)鍵對(duì)象是subject和observer苔巨。理解觀(guān)察者模式的另一種方法是發(fā)布者-訂閱者關(guān)系的工作方式版扩。
觀(guān)察者模式有四個(gè)參與者:
Subject:用于注冊(cè)觀(guān)察員。對(duì)象使用此接口注冊(cè)為觀(guān)察者侄泽,并將自己從觀(guān)察者中移除礁芦。
Observer :對(duì)象的更新接口,該接口應(yīng)在主題發(fā)生更改時(shí)通知對(duì)象悼尾。所有觀(guān)察者都需要實(shí)現(xiàn)觀(guān)察者接口飒赃。此接口有一個(gè)方法update()旷太,當(dāng)主題的狀態(tài)發(fā)生更改時(shí),將調(diào)用該方法。
ConcreteSubject :存儲(chǔ)對(duì)ConcreteObserver對(duì)象感興趣的狀態(tài)呼股。當(dāng)狀態(tài)發(fā)生變化時(shí),它向觀(guān)察者發(fā)送一個(gè)通知懂从。具體的主題總是實(shí)現(xiàn)主題接口败京。notifyobserver()方法用于在狀態(tài)發(fā)生更改時(shí)更新所有當(dāng)前的觀(guān)察者。
-
ConcreateObserver :維護(hù)對(duì)ConcreteSubject對(duì)象的引用泡仗,并實(shí)現(xiàn)Observer接口埋虹。每個(gè)觀(guān)察者注冊(cè)一個(gè)具體的主題來(lái)接收更新。
2娩怎、場(chǎng)景分析
? 體育大廳是一個(gè)了不起的體育愛(ài)好者的體育網(wǎng)站搔课。它們涵蓋了幾乎所有的體育項(xiàng)目,提供最新的新聞截亦、信息爬泥、比賽日期、特定球員或球隊(duì)的信息”廊浚現(xiàn)在袍啡,他們正計(jì)劃以短信服務(wù)的形式提供現(xiàn)場(chǎng)解說(shuō)或數(shù)十場(chǎng)比賽,但僅限于高級(jí)用戶(hù)谷遂。他們的目標(biāo)是在短時(shí)間間隔后發(fā)送短信實(shí)時(shí)比分葬馋、比賽情況和重要事件。作為用戶(hù),您需要訂閱包畴嘶,當(dāng)有現(xiàn)場(chǎng)比賽時(shí)蛋逾,您將收到一條短信到現(xiàn)場(chǎng)解說(shuō)。該站點(diǎn)還提供了一個(gè)選項(xiàng)窗悯,可以隨時(shí)從包中取消訂閱区匣。作為開(kāi)發(fā)人員,體育游說(shuō)團(tuán)要求您為他們提供這個(gè)新功能蒋院。觀(guān)察者的設(shè)計(jì)模式最適合這種情況亏钩,讓我們看看這個(gè)模式,然后為體育大廳創(chuàng)建功能欺旧。
3姑丑、代碼實(shí)現(xiàn)
public interface Subject {
public void subscribeObserver(Observer observer);
public void unSubscribeObserver(Observer observer);
public void notifyObservers();
public String subjectDetails();
}
主題界面的三個(gè)關(guān)鍵方法是:
subscribeObserver,用于訂閱觀(guān)察員辞友,或者我們可以說(shuō)注冊(cè)觀(guān)察員栅哀,以便如果主題狀態(tài)發(fā)生變化,應(yīng)該通知所有這些觀(guān)察員称龙。
unSubscribeObserver留拾,用于取消訂閱觀(guān)察者,以便如果主題狀態(tài)發(fā)生更改鲫尊,不應(yīng)通知此未訂閱的觀(guān)察者痴柔。
-
notifyobserver,當(dāng)主體狀態(tài)發(fā)生更改時(shí)疫向,此方法通知已注冊(cè)的觀(guān)察者咳蔚。
另外還有一個(gè)方法subjectDetails(),這是一個(gè)簡(jiǎn)單的方法搔驼,可以根據(jù)需要使用屹篓。在這里,它的工作是返回主題的細(xì)節(jié)〕着現(xiàn)在,讓我們看看觀(guān)察者接口
public interface Observer { public void update(String desc); public void subscribe(); public void unSubscribe(); }
update()方法由主體在觀(guān)察者上調(diào)用妄荔,以便在主體狀態(tài)發(fā)生更改時(shí)通知它泼菌。
subscribe()方法用于用主題訂閱自身。
-
unSubscribe()方法來(lái)取消訂閱主題本身啦租。
public interface Commentary { public void setDesc(String desc); }
記者使用上面的界面來(lái)更新評(píng)論對(duì)象上的實(shí)時(shí)評(píng)論哗伯,該接口只包含一個(gè)用于更改具體subject對(duì)象狀態(tài)的方法
public class CommentaryObject implements Subject,Commentary { private final List<Observer> observers; private String desc; private final String subjectDetails; public CommentaryObject(List<Observer>observers,String subjectDetails){ this.observers = observers; this.subjectDetails = subjectDetails; } @Override public void subscribeObserver(Observer observer) { observers.add(observer); } @Override public void unSubscribeObserver(Observer observer) { int index = observers.indexOf(observer); observers.remove(index); } @Override public void notifyObservers() { System.out.println(); for(Observer observer : observers){ observer.update(desc); } } @Override public void setDesc(String desc) { this.desc = desc; notifyObservers(); } @Override public String subjectDetails() { return subjectDetails; } }
上面的類(lèi)作為一個(gè)具體的主題工作,它實(shí)現(xiàn)了subject接口并提供了它的實(shí)現(xiàn)篷角。它還存儲(chǔ)對(duì)注冊(cè)到它的觀(guān)察者的引用焊刹。
public class SMSUsers implements Observer { private final Subject subject; private String desc; private String userInfo; public SMSUsers(Subject subject, String userInfo) { if (subject == null) { throw new IllegalArgumentException("No Publisher found."); } this.subject = subject; this.userInfo = userInfo; } @Override public void update(String desc) { this.desc = desc; display(); } private void display() { System.out.println("[" + userInfo + "]: " + desc); } @Override public void subscribe() { System.out.println("Subscribing " + userInfo + " to " + subject.subjectDetails() + " ..."); this.subject.subscribeObserver(this); System.out.println("Subscribed successfully."); } @Override public void unSubscribe() { System.out.println("Unsubscribing " + userInfo + " to " + subject.subjectDetails() + " ..."); this.subject.unSubscribeObserver(this); System.out.println("Unsubscribed successfully."); } }
上面的類(lèi)是實(shí)現(xiàn)觀(guān)察者接口的具體觀(guān)察者類(lèi)。它還存儲(chǔ)對(duì)它訂閱的主題的引用,并可選地存儲(chǔ)用于顯示用戶(hù)信息的userInfo變量∨翱椋現(xiàn)在俩滥,讓我們測(cè)試這個(gè)例子。
public class TestObserver { public static void main(String[] args) { Subject subject = new CommentaryObject(new ArrayList<>(), "Soccer Match[2014AUG24]"); Observer observer = new SMSUsers(subject, "Adam Warner [New York]"); observer.subscribe(); System.out.println(); Observer observer2 = new SMSUsers(subject, "Tim Ronney [London]"); observer2.subscribe(); Commentary cObject = ((Commentary) subject); cObject.setDesc("Welcome to live Soccer match"); cObject.setDesc("Current score 0-0"); System.out.println(); observer2.unSubscribe(); System.out.println(); cObject.setDesc("It’s a goal!!"); cObject.setDesc("Current score 1-0"); System.out.println(); Observer observer3 = new SMSUsers(subject, "Marrie [Paris]"); observer3.subscribe(); System.out.println(); cObject.setDesc("It’s another goal!!"); cObject.setDesc("Half-time score 2-0"); } }
正如您所看到的贺奠,最初有兩個(gè)用戶(hù)訂閱了足球比賽并開(kāi)始接收評(píng)論霜旧。但是后來(lái)有一個(gè)用戶(hù)取消了訂閱,所以用戶(hù)沒(méi)有再收到評(píng)論儡率。然后挂据,另一個(gè)用戶(hù)訂閱并開(kāi)始獲得評(píng)論。所有這一切都在不改變現(xiàn)有代碼的情況下動(dòng)態(tài)發(fā)生儿普,不僅如此崎逃,假設(shè)公司想要在電子郵件上播放評(píng)論,或者任何其他公司想要與該公司合作來(lái)播放評(píng)論眉孩。您所需要做的就是創(chuàng)建兩個(gè)新類(lèi)个绍,如UserEmail和ColCompany。并通過(guò)實(shí)現(xiàn)observer接口使它們成為主題的觀(guān)察者勺像。只要主體知道它是一個(gè)觀(guān)察者障贸,它就會(huì)提供更新。
4吟宦、何時(shí)使用觀(guān)察者模式篮洁?
- 當(dāng)抽象有兩個(gè)方面時(shí),一個(gè)依賴(lài)于另一個(gè)殃姓。將這些方面封裝在單獨(dú)的對(duì)象中袁波,可以獨(dú)立地改變和重用它們。
- 當(dāng)對(duì)一個(gè)對(duì)象的更改需要更改其他對(duì)象時(shí)蜗侈,您不知道需要更改多少對(duì)象篷牌。
- 當(dāng)一個(gè)對(duì)象應(yīng)該能夠通知其他對(duì)象而不需要假設(shè)這些對(duì)象是誰(shuí)時(shí)。換句話(huà)說(shuō)踏幻,您不希望這些對(duì)象緊密耦合枷颊。