摘要:觀察者模式是一種使用頻率非常高的設(shè)計(jì)模式,無(wú)論是移動(dòng)應(yīng)用應(yīng)用或者桌面應(yīng)用,觀察者模式幾乎無(wú)處不在,它為實(shí)現(xiàn)對(duì)象之間的聯(lián)動(dòng)提供了一套完整的解決方案,凡是涉及到一對(duì)一或者一對(duì)多的對(duì)象交互場(chǎng)景都可以使用觀察者模式。
概述觀察者模式(Observer Pattern)屬于對(duì)象行為型模式的一種,定義對(duì)象之間的一種一對(duì)多依賴關(guān)系,使得每當(dāng)一個(gè)對(duì)象狀態(tài)發(fā)生改變時(shí),其相關(guān)依賴對(duì)象皆得到通知并被自動(dòng)更新。
觀察者模式是一種使用率極高的模式,用于建立一種對(duì)象與對(duì)象之間的依賴關(guān)系,一個(gè)對(duì)象發(fā)生改變時(shí)將自動(dòng)通知其他對(duì)象,其他對(duì)象將相應(yīng)作出反應(yīng)。在觀察者模式中,發(fā)生改變的對(duì)象稱為觀察目標(biāo),而被通知的對(duì)象稱為觀察者,一個(gè)觀察目標(biāo)可以對(duì)應(yīng)多個(gè)觀察者,而且這些觀察者之間可以沒(méi)有任何相互聯(lián)系,可以根據(jù)需要增加和刪除觀察者,使得系統(tǒng)更易于擴(kuò)展。
觀察者模式的別名包括發(fā)布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監(jiān)聽(tīng)器(Source/Listener)模式或從屬者(Dependents)模式。
案例前言:觀察者模式有兩種方模型,分別是推模型和拉模型
推模型: 主題對(duì)象向觀察者推送主題的詳細(xì)信息,不管觀察者是否需要,推送的信息通常是主題對(duì)象的全部或部分?jǐn)?shù)據(jù)。該模式下如果推送數(shù)據(jù)變了觀察者都得改
拉模型: 主題對(duì)象在通知觀察者的時(shí)候,只傳遞少量信息。如果觀察者需要更具體的信息,由觀察者主動(dòng)到主題對(duì)象中獲取,相當(dāng)于是觀察者從主題對(duì)象中拉數(shù)據(jù)。一般這種 模型的實(shí)現(xiàn)中,會(huì)把主題對(duì)象自身通過(guò)update()方法傳遞給觀察者,這樣在觀察者需要獲取數(shù)據(jù)的時(shí)候,就可以通過(guò)這個(gè)引用來(lái)獲取了。
UML結(jié)構(gòu)圖
抽象主題(Subject)角色: 將觀察者對(duì)象的引用保存在一個(gè)聚集(比如ArrayList對(duì)象)里,每個(gè)主題都可以有任何數(shù)量的觀察者。抽象主題提供接口,可以增加和刪除觀察者對(duì)象。抽象主題角色又叫做抽象被觀察者(Observable)角色。
具體主題(ConcreteSubject)角色: 將有關(guān)狀態(tài)存入具體觀察者對(duì)象;在具體主題的內(nèi)部狀態(tài)改變時(shí),給所有登記過(guò)的觀察者發(fā)出通知。具體主題角色又叫做具體被觀察者(Concrete Observable)角色。
抽象觀察者(Observer)角色: 為所有的具體觀察者定義一個(gè)更新接口,在得到主題的通知時(shí)更新自己。
具體觀察者(ConcreteObserver)角色: 觀察者的具體實(shí)現(xiàn)對(duì)象,實(shí)現(xiàn)抽象觀察者角色所要求的更新接口,以便使本身的狀態(tài)與主題的狀態(tài)相協(xié)調(diào)。如果需要,具體觀察者角色可以保持一個(gè)指向具體主題對(duì)象的引用。
推模式1.定義目標(biāo)對(duì)象,它知道觀察它的觀察者,并提供注冊(cè)和刪除觀察者的接口
class Subject { /** * 用來(lái)保存注冊(cè)的觀察者對(duì)象 */ private Listobservers = new ArrayList<>(); /** * 注冊(cè)觀察者對(duì)象 * * @param observer 觀察者對(duì)象 */ void attach(Observer observer) { observers.add(observer); } /** * 通知所有注冊(cè)的觀察者對(duì)象 */ void notifyObservers(String newState) { for (Observer observer : observers) { observer.update(newState); } } }
2.具體的目標(biāo)對(duì)象,負(fù)責(zé)把有關(guān)狀態(tài)存入到相應(yīng)的觀察者對(duì)象,并在自己狀態(tài)發(fā)生改變時(shí),通知各個(gè)觀察者
class ConcreteSubject extends Subject { private String subjectState; public String getSubjectState() { return subjectState; } public void change(String subjectState) { this.subjectState = subjectState; //狀態(tài)發(fā)生改變,通知各個(gè)觀察者 this.notifyObservers(subjectState); } }
3.創(chuàng)建觀察者接口,定義一個(gè)更新的接口給那些在目標(biāo)發(fā)生改變的時(shí)候被通知的對(duì)象
interface Observer { /** * 更新的接口 * * @param subject 傳入目標(biāo)對(duì)象,好獲取相應(yīng)的目標(biāo)對(duì)象的狀態(tài) */ void update(String subject); }
4.具體觀察者對(duì)象,實(shí)現(xiàn)更新的方法,使自身的狀態(tài)和目標(biāo)的狀態(tài)保持一致
class ConcreteObserver implements Observer { @Override public void update(String newState) { //具體的更新實(shí)現(xiàn) //這里可能需要更新觀察者的狀態(tài),使其與目標(biāo)的狀態(tài)保持一致 System.out.println("接收到:" + newState); } }
5.創(chuàng)建推模型客戶端,用于測(cè)試
public class PushClient { public static void main(String[] args) { //創(chuàng)建主題對(duì)象 ConcreteSubject subject = new ConcreteSubject(); //創(chuàng)建觀察者對(duì)象 Observer observer = new ConcreteObserver(); //將觀察者對(duì)象登記到主題對(duì)象上 subject.attach(observer); //改變主題對(duì)象的狀態(tài) subject.change("push state"); } }
6.運(yùn)行結(jié)果
接收到:push state拉模式
1.定義目標(biāo)對(duì)象,它知道觀察它的觀察者,并提供注冊(cè)和刪除觀察者的接口
class Subject { /** * 用來(lái)保存注冊(cè)的觀察者對(duì)象 */ private Listobservers = new ArrayList<>(); /** * 注冊(cè)觀察者對(duì)象 * * @param observer 觀察者對(duì)象 */ public void attach(Observer observer) { observers.add(observer); } /** * 通知所有注冊(cè)的觀察者對(duì)象 */ public void notifyObservers() { for (Observer observer : observers) { // 注意這句代碼" observer.update(this); } } }
2.具體的目標(biāo)對(duì)象,負(fù)責(zé)把有關(guān)狀態(tài)存入到相應(yīng)的觀察者對(duì)象,并在自己狀態(tài)發(fā)生改變時(shí),通知各個(gè)觀察者
class ConcreteSubject extends Subject { /** * 示意,目標(biāo)對(duì)象的狀態(tài) */ private String subjectState; public String getSubjectState() { return subjectState; } public void change(String subjectState) { this.subjectState = subjectState; //狀態(tài)發(fā)生改變,通知各個(gè)觀察者 this.notifyObservers(); } }
3.創(chuàng)建觀察者接口,定義一個(gè)更新的接口給那些在目標(biāo)發(fā)生改變的時(shí)候被通知的對(duì)象
interface Observer { /** * 更新的接口 * * @param subject 傳入目標(biāo)對(duì)象,好獲取相應(yīng)的目標(biāo)對(duì)象的狀態(tài) */ void update(Subject subject); }
4.具體觀察者對(duì)象,實(shí)現(xiàn)更新的方法,使自身的狀態(tài)和目標(biāo)的狀態(tài)保持一致
class ConcreteObserver implements Observer { /** * 示意,觀者者的狀態(tài) */ private String observerState; @Override public void update(Subject subject) { //具體的更新實(shí)現(xiàn) //這里可能需要更新觀察者的狀態(tài),使其與目標(biāo)的狀態(tài)保持一致 observerState = ((ConcreteSubject) subject).getSubjectState(); System.out.println("接收到:" + observerState); } }
5.創(chuàng)建拉模型客戶端,用于測(cè)試
public class PullClient { public static void main(String[] args) { //創(chuàng)建主題對(duì)象 ConcreteSubject subject = new ConcreteSubject(); //創(chuàng)建觀察者對(duì)象 Observer observer = new ConcreteObserver(); //將觀察者對(duì)象登記到主題對(duì)象上 subject.attach(observer); //改變主題對(duì)象的狀態(tài) subject.change("pull state"); } }
6.運(yùn)行結(jié)果
接收到:pull state
上文說(shuō)過(guò)推模型是假定主題對(duì)象知道觀察者需要的數(shù)據(jù),這種模型下如果數(shù)據(jù)發(fā)生變更會(huì)造成極大的影響;而拉模型是主題對(duì)象不知道觀察者具體需要什么數(shù)據(jù),沒(méi)有辦法的情況下,干脆把自身傳遞給觀察者,讓觀察者自己去按需要取值。由此可見(jiàn):拉模式的適用范圍更廣;
JDK中應(yīng)用對(duì)于觀察者模式,其實(shí)Java已經(jīng)為我們提供了已有的接口和類。對(duì)于訂閱者(Subscribe,觀察者)Java為我們提供了一個(gè)接口。
UML圖
在JAVA語(yǔ)言的 java.util 庫(kù)里面,提供了一個(gè)Observable類以及一個(gè)Observer接口,構(gòu)成JAVA語(yǔ)言對(duì)觀察者模式的支持。
Observer: 只定義了一個(gè) update() 方法,當(dāng)被觀察者對(duì)象的狀態(tài)發(fā)生變化時(shí),被觀察者對(duì)象的 notifyObservers() 方法就會(huì)調(diào)用這一方法。
public interface Observer { void update(Observable o, Object arg); }
Observable: 充當(dāng)觀察目標(biāo)類,在Observable中定義了一個(gè)向量Vector來(lái)存儲(chǔ)觀察者對(duì)象。一個(gè)觀察目標(biāo)類可以有多個(gè)觀察者對(duì)象,每個(gè)觀察者對(duì)象都是實(shí)現(xiàn)Observer接口的對(duì)象。在被觀察者發(fā)生變化時(shí),會(huì)調(diào)用Observable的notifyObservers()方法,此方法調(diào)用所有的具體觀察者的update()方法, 從而使所有的觀察者都被通知更新自己。
setChanged() 設(shè)置一個(gè)內(nèi)部標(biāo)記變量,代表被觀察者對(duì)象的狀態(tài)發(fā)生了變化。
notifyObservers()調(diào)用所有登記過(guò)的觀察者對(duì)象的update()方法,使這些觀察者對(duì)象可以更新自己。
public class Observable { private boolean changed = false; //是否改變狀態(tài),每次都需要設(shè)置,表示內(nèi)容發(fā)生變化 private Vector小案例obs; //Vector利用同步方法來(lái)線程安全,線程安全在多線程情況下不會(huì)造成數(shù)據(jù)混亂 /** Construct an Observable with zero Observers. */ public Observable() { obs = new Vector<>(); } public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } //通知方法,用于在方法內(nèi)部循環(huán)調(diào)用向量中每一個(gè)觀察者的update()方法。 public void notifyObservers() { notifyObservers(null); } public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { if (!changed) //狀態(tài)值未改變時(shí)返回,不通知 return; arrLocal = obs.toArray(); //將Vector轉(zhuǎn)換成數(shù)組 clearChanged(); //重置狀態(tài) } for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); } public synchronized void deleteObservers() { obs.removeAllElements(); } protected synchronized void setChanged() { changed = true; } protected synchronized void clearChanged() { changed = false; } public synchronized boolean hasChanged() { return changed; } public synchronized int countObservers() { return obs.size(); } }
1.定義兩個(gè)實(shí)現(xiàn)了實(shí)現(xiàn)java.util.Observer接口的觀察者
class SubscribeReader implements Observer { @Override public void update(Observable o, Object arg) { System.out.println("開(kāi)始讀取:" + ((Publish) o).getMessage()); } } class SubscribeWrite implements Observer { @Override public void update(Observable o, Object arg) { System.out.println("開(kāi)始寫入:" + ((Publish) o).getMessage()); } }
2.創(chuàng)建繼承java.util.Observable的通知者
class Publish extends Observable { private String message; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; //改變通知者的狀態(tài) super.setChanged(); //調(diào)用父類Observable方法,通知所有觀察者 super.notifyObservers(); } }
3.創(chuàng)建測(cè)試客戶端
public class Client { public static void main(String[] args) { Publish publish = new Publish(); // 遵循FIFO 模型 先進(jìn)后出 SubscribeWrite write = new SubscribeWrite(); SubscribeReader reader = new SubscribeReader(); publish.addObserver(reader); publish.addObserver(write); publish.setMessage("Hello Battcn"); publish.setMessage("QQ:1837307557"); publish.setMessage("Email:1837307557@qq.com"); } }
4.運(yùn)行結(jié)果
開(kāi)始寫入:Hello Battcn 開(kāi)始讀取:Hello Battcn 開(kāi)始寫入:QQ:1837307557 開(kāi)始讀取:QQ:1837307557 開(kāi)始寫入:Email:1837307557@qq.com 開(kāi)始讀取:Email:1837307557@qq.com觀察者模式與MVC
在當(dāng)前流行的MVC(Model-View-Controller)架構(gòu)中也應(yīng)用了觀察者模式,MVC是一種架構(gòu)模式,它包含三個(gè)角色:模型(Model),視圖(View)和控制器(Controller)。其中模型可對(duì)應(yīng)于觀察者模式中的觀察目標(biāo),而視圖對(duì)應(yīng)于觀察者,控制器可充當(dāng)兩者之間的中介者。當(dāng)模型層的數(shù)據(jù)發(fā)生改變時(shí),視圖層將自動(dòng)改變其顯示內(nèi)容。
總結(jié)實(shí)現(xiàn)的關(guān)鍵是要建立觀察者和被觀察者之間的聯(lián)系、比如在被觀察者類中有個(gè)集合是用于存放觀察者的、當(dāng)被檢測(cè)的東西發(fā)生改變的時(shí)候就要通知所有觀察者。在被觀察者中要提供一些對(duì)所有觀察者管理的一些方法.目的是添加或者刪除一些觀察者.這樣才能讓被觀察者及時(shí)的通知觀察者關(guān)系的狀態(tài)已經(jīng)改變、并且調(diào)用觀察者通用的方法將變化傳遞過(guò)去。
在實(shí)現(xiàn)觀察者模式,如果JDK的Observable類和一個(gè)Observer接口能滿足需求,直接復(fù)用即可,無(wú)需自己編寫抽象觀察者、抽象主題類;
但是,java.util.Observable是一個(gè)類而不是接口,你必須設(shè)計(jì)一個(gè)類繼承它。如果某個(gè)類想同時(shí)具有Observable類和另一個(gè)超類的行為,由于java不支持多重繼承。所以這個(gè)時(shí)候就需要自己實(shí)現(xiàn)一整套觀察者模式。
優(yōu)點(diǎn)
可實(shí)現(xiàn)表示層和數(shù)據(jù)邏輯層的分離,定義了穩(wěn)定的消息更新傳遞機(jī)制,并抽象了更新接口,使得可以有各種各樣不同的表示層充當(dāng)具體觀察者角色(Model/View)。
支持廣播通信,觀察目標(biāo)會(huì)向所有已注冊(cè)的觀察者對(duì)象發(fā)送通知,簡(jiǎn)化了一對(duì)多系統(tǒng)設(shè)計(jì)的難度(Publish/Subscribe)。
實(shí)現(xiàn)動(dòng)態(tài)聯(lián)動(dòng)。由于觀察者模式對(duì)觀察者注冊(cè)實(shí)行管理,那就可以在運(yùn)行期間,通過(guò)動(dòng)態(tài)的控制注冊(cè)的觀察者,來(lái)控制某個(gè)動(dòng)作的聯(lián)動(dòng)范圍,從而實(shí)現(xiàn)動(dòng)態(tài)聯(lián)動(dòng)。
缺點(diǎn)
如果一個(gè)被觀察者對(duì)象有很多直接和間接的觀察者,那么將所有的觀察者都通知到會(huì)花費(fèi)很多時(shí)間。
如果在觀察者和被觀察者之間有循環(huán)依賴的話,被觀察者會(huì)觸發(fā)它們形成循環(huán)調(diào)用,可能導(dǎo)致系統(tǒng)崩潰。
觀察者模式?jīng)]有相應(yīng)的機(jī)制讓觀察者知道被觀察者對(duì)象是怎么發(fā)生變化的,而僅僅只是知道被觀察者發(fā)生了變化。
觀察者模式是一種使用頻率非常高的設(shè)計(jì)模式,無(wú)論是移動(dòng)應(yīng)用、Web應(yīng)用或者桌面應(yīng)用,觀察者模式幾乎無(wú)處不在,它為實(shí)現(xiàn)對(duì)象之間的聯(lián)動(dòng)提供了一套完整的解決方案,凡是涉及到一對(duì)一或者一對(duì)多的對(duì)象交互場(chǎng)景都可以使用觀察者模式。
說(shuō)點(diǎn)什么參考文獻(xiàn):http://www.cnblogs.com/JsonShare/p/7270546.html
全文代碼:https://gitee.com/battcn/design-pattern/tree/master/Chapter17/battcn-observer
個(gè)人QQ:1837307557
battcn開(kāi)源群(適合新手):391619659
微信公眾號(hào):battcn(歡迎調(diào)戲)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/70769.html
摘要:在前面的文章中介紹過(guò)觀察者模式及并發(fā)編程的基礎(chǔ)知識(shí),為了讓大家更好的了解觀察者模式故而特意寫了這篇番外概述在多線程下我們需要知道當(dāng)前執(zhí)行線程的狀態(tài)是什么比如運(yùn)行,關(guān)閉,異常等狀態(tài)的通知,而且不僅僅是更新當(dāng)前頁(yè)面。 在前面的文章中介紹過(guò) 觀察者模式 及 并發(fā)編程的基礎(chǔ)知識(shí),為了讓大家更好的了解觀察者模式故而特意寫了這篇番外.. 概述 在Java多線程下,我們需要知道當(dāng)前執(zhí)行線程的狀態(tài)是...
摘要:懶漢非線程安全,需要用一定的風(fēng)騷操作控制,裝逼失敗有可能導(dǎo)致看一周的海綿寶寶餓漢天生線程安全,的時(shí)候就已經(jīng)實(shí)例化好,該操作過(guò)于風(fēng)騷會(huì)造成資源浪費(fèi)單例注冊(cè)表初始化的時(shí)候,默認(rèn)單例用的就是該方式特點(diǎn)私有構(gòu)造方法,只能有一個(gè)實(shí)例。 單例設(shè)計(jì)模式(Singleton Pattern)是最簡(jiǎn)單且常見(jiàn)的設(shè)計(jì)模式之一,主要作用是提供一個(gè)全局訪問(wèn)且只實(shí)例化一次的對(duì)象,避免多實(shí)例對(duì)象的情況下引起邏輯性錯(cuò)...
摘要:設(shè)計(jì)模式的分類經(jīng)典應(yīng)用框架中常見(jiàn)的設(shè)計(jì)模式分為三類創(chuàng)建型模式對(duì)類的實(shí)例化過(guò)程的抽象。對(duì)象的結(jié)構(gòu)模式是動(dòng)態(tài)的。對(duì)象的行為模式則使用對(duì)象的聚合來(lái)分配行為。設(shè)計(jì)模式是個(gè)好東西,以后肯定還要進(jìn)一步的學(xué)習(xí),并且在項(xiàng)目中多實(shí)踐,提升自己的設(shè)計(jì)能力。 什么是設(shè)計(jì)模式? Christopher Alexander?說(shuō)過(guò):每一個(gè)模式描述了一個(gè)在我們周圍不斷重復(fù)發(fā)生的問(wèn)題,以及該問(wèn)題的解決方案的核心。這樣...
摘要:一個(gè)對(duì)象維持一系列依賴于它觀察者的對(duì)象,將有關(guān)狀態(tài)的任何變更自動(dòng)通知給它們。觀察者模式的實(shí)現(xiàn)模擬擁有的一系列依賴使用擴(kuò)展對(duì)象模擬目標(biāo)和在觀察者列表上添加刪除或通知觀察者 一個(gè)對(duì)象(subject)維持一系列依賴于它(觀察者)的對(duì)象,將有關(guān)狀態(tài)的任何變更自動(dòng)通知給它們。 當(dāng)一個(gè)目標(biāo)需要告訴觀察者發(fā)生了什么有趣的事情,它會(huì)向觀察者廣播一個(gè)通知 當(dāng)我們不再希望某個(gè)特定的觀察者獲取其注冊(cè)目...
摘要:扎實(shí)基礎(chǔ)幸好自己之前花了大力氣去給自己打基礎(chǔ),讓自己現(xiàn)在的基礎(chǔ)還算不錯(cuò)。 寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺(jué)得排版難看,請(qǐng)點(diǎn)擊 下面鏈接 或者 拉到 下面關(guān)注公眾號(hào)也可以吧 【Vue原理】Vue源碼閱讀總結(jié)大會(huì) - 序 閱讀源碼是需...
閱讀 2914·2021-11-24 09:39
閱讀 1723·2021-09-28 09:35
閱讀 1178·2021-09-06 15:02
閱讀 1448·2021-07-25 21:37
閱讀 2833·2019-08-30 15:53
閱讀 3711·2019-08-30 14:07
閱讀 767·2019-08-30 11:07
閱讀 3604·2019-08-29 18:36