前言
觀察者模式也是對象行為模式的一種,又叫做發表-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、 咱們目前用的最多的就是各種MQ(Message Queue)都是基於這個模式的思想來實現的,生產者產生數據放到一個隊列中,消費者觀察生產者的消息隊列的變化,從而接收消息,執行消費者本身的邏輯。
觀察者模式
概念介紹
觀察者模式定義了一個一對多的依賴關系,讓一個或多個觀察者對象監察一個主題對象。這樣一個主題對象在狀態上的變化能夠通知所有的依賴於此對象的那些觀察者對象,使這些觀察者對象能夠自動更新。

這些觀察者之間沒有任何關聯,可以根據業務需要增加刪除觀察者,易於系統擴展。
舉例
還是來舉實際的例子,來介紹設計模式,畢竟設計模式是一種抽象的東西,需要落到真正的實現中才能體現出它的價值。當我們在網上購物時,看到一件自己比較喜歡的商品,但是最近手頭有點緊(已經開始吃土了),所以會先關注一下這個商品,一般的購物網站上都會有關注此商品這么一個功能的。為了就是當商品降價打折或是其他變化的時候能夠通知到所有關注此商品的顧客。那么我們就以這個功能為例子來使用觀察者模式實現一下。
抽象主題類
/** * 抽象被觀察類 */ @Getter public abstract class Observable { //觀察者集合,存儲關注商品的所有顧客 protected List<Observer> observerList = Lists.newArrayList(); /** * 添加觀察者(當一個顧客選擇了關注商品時添加到觀察者集合中) * @param observer 觀察者 */ public void attach(Observer observer){ observerList.add(observer); } /** * 注銷觀察者(取消關注商品) * @param observer 觀察者 */ public void detach(Observer observer){ observerList.remove(observer); } /** * 通知觀察者的方法 */ public abstract void notice(Object obj); }
商品類
/** * 商品類 */ @Getter //lombok get方法 @AllArgsConstructor //lombok 以所有屬性為參數的構造方法 @NoArgsConstructor //lombok 沒有參數的構造方法 public class Product extends Observable { /** 商品名稱 */ protected String name; /** 商品價格*/ protected BigDecimal price; /** * 商品名稱變更 * @param name 商品名稱 */ public void setName(String name){ this.name = name; //通知觀察者 notice(name); } /** * 價格變更 * @param price 商品價格 */ public void setPrice(BigDecimal price){ this.price = price; //通知觀察者 notice(price); } /** * 通知觀察者的方法 */ @Override public void notice(Object obj) { if(Objects.nonNull(observerList)&&observerList.size()>0){ observerList.forEach((Observer observer) -> observer.update(obj)); } } }
抽象觀察者類
/** * 抽象觀察者 */ public abstract class Observer { /** * 更新 * @param obj 更新對象 */ public abstract void update(Object obj); }
名稱觀察者
/** * 名稱觀察者 */ public class NameObserver extends Observer { /** * 更新 * @param obj 更新對象 */ @Override public void update(Object obj) { if(obj instanceof String){ String name = (String) obj; System.out.println("您關注的商品名稱發生了變化,最新的商品名稱是"+name); } } }
價格觀察者
/** * 價格觀察者 */ public class PriceObserver extends Observer{ /** * 更新 * * @param obj 更新對象 */ @Override public void update(Object obj) { if(obj instanceof BigDecimal){ BigDecimal price = (BigDecimal)obj; System.out.println("您關注的商品價格發生了變化,最新的商品價格是:"+price); } } }
測試類
public class Test { public static void main(String[] args) { Product product = new Product("iphoneX",new BigDecimal(8999)); System.out.println("您關注的商品的名稱是:"+product.getName()+",價格是:"+product.getPrice()); //創建觀察者 NameObserver nameObserver = new NameObserver(); PriceObserver priceObserver = new PriceObserver(); //加入觀察者 product.attach(nameObserver); product.attach(priceObserver); //產生變化,通知觀察者 product.setName("iphoneX Max"); product.setPrice(new BigDecimal(12999)); } }
運行結果:
您關注的商品的名稱是:iphoneX,價格是:8999
您關注的商品名稱發生了變化,最新的商品名稱是iphoneX Max
您關注的商品價格發生了變化,最新的商品價格是:12999
通過上面的運行結果我們就能看出來,當商品名稱或價格發生變化時,會通知到相應的觀察者,這就是觀察者模式的具體應用了。那么通過例子我們也可以看出來觀察者模式具體是由哪些角色組成的。
觀察者模式的結構
觀察者模式結構如下圖

在觀察者模式中存在如下幾種角色:
抽象主題角色(Subject):抽象主題角色把所有的觀察者對象的引用保存在一個列表里;每個主題都可以有任何數量的觀察者。主題提供一個接口,可以加上或撤銷觀察者對象;主題角色又被稱為被觀察者角色。可以用抽象類或接口來實現。
抽象觀察者角色(Observer):為所有的具體觀察者定義一個接口,在得到通知時更新自己。抽象觀察者角色通常是用一個抽象類或一個接口來實現;當然也可以用具體的類來實現。
具體主題角色(ConcreteSubject):具體主題保存對具體觀察者對象有用的內部狀態,在這種狀態改變時,給其觀察者發出一個具體的通知,具體主題角色又被稱為具體被觀察者角色。
具體觀察者角色(ConcreteObserver):具體觀察者角色用於保存一個指向具體主題對象的引用,和一個與主題的狀態相符的狀態。具體觀察者角色實現抽象觀察者角色所要求的更新自己的接口,以便使本身的狀態與主題的狀態對應。
總結
觀察者模式是一種使用頻率比較高的設計模式,凡是涉及到一對一或一對多的對象交互場景都可以使用觀察者模式。
觀察者模式的主要優點
1、觀察者模式可以實現表示層和數據邏輯層的分離,定義了穩定的消息更新傳遞機制,並抽象了更新接口,使得可以有各種各樣不同的表示層充當觀察者角色。
2、觀察者模式在觀察目標和觀察者之間建立一個抽象耦合。觀察目標只需要維持一個抽象觀察者的集合,無須了解其具體觀察者。由於觀察目標和觀察者沒有緊密地耦合在一起,因此它們可以屬於不同的抽象化層次。
3、觀察者模式支持廣播通信,觀察目標會向所有已注冊的觀察者對象發送通知,簡化了一對多系統設計的難度。
4、觀察者模式滿足“開閉原則”的要求,增加新的具體觀察者無須修改原有系統代碼,在具體觀察者與觀察目標之間不存在關聯關系的情況下,增加新的觀察目標也很方便。
觀察者模式的主要缺點
1、如果一個觀察目標對象有很多直接和間接觀察者,將所有的觀察者都通知到會花費很多時間。
2、如果在觀察者和觀察目標之間存在循環依賴,觀察目標會觸發它們之間進行循環調用,可能導致系統崩潰。
3、觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎么發生變化的,而僅僅只是知道觀察目標發生了變化。
適用場景
1、一個對象的改變將會導致一個或多個對象的改變,不清楚具體有多少對象以及這些被影響的對象是誰的情況。
2、如果有這樣一個影響鏈的情況下也可以使用,例如A的改變會影響B,B的改變會影響C......,可以使用觀察者模式設計一個鏈式觸發機制。
想了解更多的設計模式請查看Java設計模式學習記錄-GoF設計模式概述。

這個是我的個人公眾號,文章以后也會同步到公眾號上去,歡迎關注。
