Java設(shè)計(jì)模式之觀察者模式解析
概述
定義
觀察者模式,又被稱為發(fā)布-訂閱(Publish / Subscribe)模式,它定義了一種一對(duì)多的依賴關(guān)系,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽某一個(gè)主題對(duì)象。這個(gè)主題對(duì)象在狀態(tài)變化時(shí),會(huì)通知所有的觀察者對(duì)象,使他們能夠自動(dòng)更新自己。
結(jié)構(gòu)
觀察者模式 (Observer Pattern) 中有如下角色:
- Subject:抽象主題(抽象被觀察者),抽象主題角色把所有觀察者對(duì)象保存在一個(gè)集合里,每個(gè)主題都可以有任意數(shù)量的觀察者,抽象主題提供一個(gè)接口,可以增加和刪除觀察者對(duì)象。
- ConcreteSubject:具體主題(具體被觀察者),該角色將有關(guān)狀態(tài)存入具體觀察者對(duì)象,在具體主題的內(nèi)部狀態(tài)發(fā)生改變時(shí),給所有注冊(cè)過的觀察者發(fā)送通知。
- Observer:抽象觀察者,是觀察者的抽象類,它定義了一個(gè)更新接口,使得在得到主題更改通知時(shí)更新自己。
- ConcrereObserver:具體觀察者,實(shí)現(xiàn)抽象觀察者定義的更新接口,以便在得到主題更改通知時(shí)更新自身的狀態(tài)。
案例實(shí)現(xiàn)
【例】微信公眾號(hào)
在使用微信公眾號(hào)時(shí),大家都會(huì)有這樣的體驗(yàn),當(dāng)你關(guān)注的公眾號(hào)中有新內(nèi)容更新的話,它就會(huì)推送給關(guān)注公眾號(hào)的微信用戶端。我們使用觀察者模式來模擬這樣的場(chǎng)景,微信用戶就是觀察者,微信公眾號(hào)是被觀察者(主題角色),有多個(gè)的微信用戶關(guān)注了程序猿這個(gè)公眾號(hào)。
類圖如下:
代碼如下:
- 抽象觀察者類,里面定義一個(gè)更新的方法
public interface Observer { void update(String message); }
- 具體觀察者類,微信用戶是觀察者,里面實(shí)現(xiàn)了更新的方法
public class WeixinUser implements Observer { // 微信用戶名 private String name; public WeixinUser(String name) { this.name = name; } @Override public void update(String message) { System.out.println(name + "-" + message); } }
- 抽象主題類(被觀察者),提供了 attach、detach、notify 三個(gè)方法
public interface Subject { // 增加訂閱者(觀察者對(duì)象) public void attach(Observer observer); // 刪除訂閱者(觀察者對(duì)象) public void detach(Observer observer); // 通知訂閱者更新消息 public void notify(String message); }
- 微信公眾號(hào)是具體主題類(具體被觀察者),里面存儲(chǔ)了訂閱該公眾號(hào)的微信用戶,并實(shí)現(xiàn)了抽象主題中的方法
public class SubscriptionSubject implements Subject { // 儲(chǔ)存訂閱公眾號(hào)的微信用戶 private List<Observer> weixinUserlist = new ArrayList<Observer>(); @Override public void attach(Observer observer) { weixinUserlist.add(observer); } @Override public void detach(Observer observer) { weixinUserlist.remove(observer); } @Override public void notify(String message) { for (Observer observer : weixinUserlist) { observer.update(message); } } }
- 客戶端程序
public class Client { public static void main(String[] args) { // 1.創(chuàng)建公眾號(hào)對(duì)象 SubscriptionSubject mSubscriptionSubject = new SubscriptionSubject(); // 2.創(chuàng)建微信用戶 WeiXinUser user1 = new WeiXinUser("孫悟空"); WeiXinUser user2 = new WeiXinUser("豬悟能"); WeiXinUser user3 = new WeiXinUser("沙悟凈"); // 3.訂閱公眾號(hào) mSubscriptionSubject.attach(user1); mSubscriptionSubject.attach(user2); mSubscriptionSubject.attach(user3); // 4.公眾號(hào)更新發(fā)出消息給訂閱的微信用戶 mSubscriptionSubject.notify("傳智黑馬的專欄更新了"); } }
輸出
孫悟空-傳智黑馬的專欄更新了
豬悟能-傳智黑馬的專欄更新了
沙悟凈-傳智黑馬的專欄更新了
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 降低了目標(biāo)與觀察者之間的耦合關(guān)系,兩者之間是抽象耦合關(guān)系。
- 被觀察者發(fā)送通知,所有注冊(cè)的觀察者都會(huì)收到信息【可以實(shí)現(xiàn)廣播機(jī)制】
缺點(diǎn)
- 如果觀察者非常多的話,那么所有的觀察者收到被觀察者發(fā)送的通知會(huì)耗時(shí)
- 如果被觀察者有循環(huán)依賴的話,那么被觀察者發(fā)送通知會(huì)使觀察者循環(huán)調(diào)用,會(huì)導(dǎo)致系統(tǒng)崩潰
使用場(chǎng)景
- 對(duì)象間存在一對(duì)多關(guān)系,一個(gè)對(duì)象的狀態(tài)發(fā)生改變會(huì)影響其他對(duì)象。
- 當(dāng)一個(gè)抽象模型有兩個(gè)方面,其中一個(gè)方面依賴于另一方面時(shí)。
JDK中提供的實(shí)現(xiàn)-Observable/Observer
在 Java 中,通過 java.util.Observable(抽象主題角色類) 和 java.util.Observer(抽象觀察角色接口) 定義了觀察者模式,只要實(shí)現(xiàn)它們的子類就可以編寫觀察者模式實(shí)例。
Observable 類
Observable 類是抽象目標(biāo)類(被觀察者),它有一個(gè) Vector 集合成員變量,用于保存所有要通知的觀察者對(duì)象。
private Vector<Observer> obs; // 在無參構(gòu)造方法中賦值 public Observable() { obs = new Vector<>(); }
下面來介紹它最重要的 3 個(gè)方法。
- void addObserver(Observer o) 方法:用于將新的觀察者對(duì)象添加到集合中。
public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); // 如果不存在則添加到集合中 if (!obs.contains(o)) { obs.addElement(o); } }
- void notifyObservers(Object arg) 方法:調(diào)用集合中的所有觀察者對(duì)象的 update 方法,通知它們數(shù)據(jù)發(fā)生改變。通常越晚加入集合的觀察者越先得到通知。
public void notifyObservers() { notifyObservers(null); } || \/ public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { // 只有changed為true才可以執(zhí)行到下面的for循環(huán) if (!changed) return; // 集合轉(zhuǎn)數(shù)組 arrLocal = obs.toArray(); // 這個(gè)方法會(huì)將changed置為false 因?yàn)榧磳⑦M(jìn)行通知 clearChanged(); } // 倒序遍歷數(shù)組 所以這就是為什么越晚加入集合的觀察者越先得到通知 for (int i = arrLocal.length-1; i>=0; i--) // 逐一通知觀察者(調(diào)用update方法) ((Observer)arrLocal[i]).update(this, arg); }
- void setChange() 方法:用來設(shè)置一個(gè) boolean 類型的內(nèi)部標(biāo)志,注明目標(biāo)對(duì)象發(fā)生了變化。當(dāng)它為 true 時(shí),notifyObservers() 才會(huì)通知觀察者。
protected synchronized void setChanged() { changed = true; }
Observer 接口
Observer 接口是抽象觀察者,它監(jiān)視目標(biāo)對(duì)象的變化,當(dāng)目標(biāo)對(duì)象發(fā)生變化時(shí),觀察者得到通知,并調(diào)用 update 方法,進(jìn)行相應(yīng)的工作。
【例】警察抓小偷
警察抓小偷也可以使用觀察者模式來實(shí)現(xiàn),警察是觀察者,小偷是被觀察者。代碼如下:
小偷是一個(gè)被觀察者,所以需要繼承 Observable 類
public class Thief extends Observable { private String name; public Thief(String name) { this.name = name; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void steal() { System.out.println("小偷:我偷東西了,有沒有人來抓我!?。?); super.setChanged(); // changed = true,調(diào)用通知的方法 才會(huì)執(zhí)行觀察者的update方法 super.notifyObservers(); } }
警察是一個(gè)觀察者,所以需要讓其實(shí)現(xiàn) Observer 接口
public class Policemen implements Observer { private String name; public Policemen(String name) { this.name = name; } public void setName(String name) { this.name = name; } public String getName() { return name; } @Override public void update(Observable o, Object arg) { System.out.println(this.getName() + "警察:" + ((Thief) o).getName() + ",我已經(jīng)盯你很久了,你可以保持沉默,但你所說的將成為呈堂證供!?。?); } }
客戶端代碼
public class Client { public static void main(String[] args) { // 創(chuàng)建小偷對(duì)象 Thief t = new Thief("隔壁老王"); // 創(chuàng)建警察對(duì)象 Policemen p = new Policemen("小李"); // 讓警察盯著小偷(訂閱) t.addObserver(p); // 小偷偷東西(發(fā)布) t.steal(); } }
輸出
小偷:我偷東西了,有沒有人來抓我!??!
小李警察:隔壁老王,我已經(jīng)盯你很久了,你可以保持沉默,但你所說的將成為呈堂證供!?。?/p>
到此這篇關(guān)于Java設(shè)計(jì)模式之觀察者模式解析的文章就介紹到這了,更多相關(guān)Java觀察者模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot+logback默認(rèn)日志的配置和使用方式
這篇文章主要介紹了SpringBoot+logback默認(rèn)日志的配置和使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05解決springcloud-eureka注冊(cè)時(shí)的ip問題
這篇文章主要介紹了解決springcloud-eureka注冊(cè)時(shí)的ip問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08java使用Socket實(shí)現(xiàn)文件上傳功能
這篇文章主要為大家詳細(xì)介紹了java使用Socket實(shí)現(xiàn)文件上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02