主頁 > 軟體設計 > 觀察者模式(學習筆記)

觀察者模式(學習筆記)

2021-10-11 12:31:23 軟體設計

  1. 意圖

  定義物件間的一種一對多的依賴關系,當一個物件的狀態發生變化時,所有依賴于它的物件都得到通知并被自動更新

  2. 動機    

  假設這樣一種情況,顧客對某個特定品牌的產品非常感興趣(例如最新型號的 iPhone 手機),而該產品很快將會在商店里出售,顧客可以每天來商店看看產品是否到貨,但如果商品尚未到貨時,絕大多數來到商店的顧客都會空手而歸,另一方面,每次新產品到貨時,商店可以向所有顧客發送郵件(可能會被視為垃圾郵件),這樣,部分顧客就無需反復前往商店了,但也可能會惹惱對新產品沒有興趣的其他顧客,

  我們似乎遇到了一個矛盾:要么讓顧客浪費時間檢查產品是否到貨,要么讓商店浪費資源去通知沒有需求的顧客,觀察者模式可以解決這一問題,  

  觀察者模式為發布者(將自身的狀態改變通知給其他物件)類添加訂閱機制,讓每個物件都能訂閱或取消訂閱發布者事件流,該機制包括:

  1)一個用于存盤訂閱者(所有希望關注發布者狀態變化的其他物件)物件參考的串列成員變數;

  2)幾個用于添加或洗掉該串列中訂閱者的公有方法,

        

  這樣,無論何時發生了重要的發布者事件,它都要遍歷訂閱者并呼叫其物件的特定通知方法,在實際應用中可能會有十幾個不同的訂閱者類跟蹤著同一個發布者類的事件, 我們不希望發布者與所有這些類相耦合的,因此,所有訂閱者都必須實作同樣的介面,發布者僅通過該介面與訂閱者互動,介面中必須宣告通知方法及其引數,這樣發布者在發出通知時還能傳遞一些背景關系資料,如果在應用中存在不同型別的發布者,且希望一個訂閱者可以同時訂閱多個發布者,需要讓所有訂閱者遵循相同的介面,并在該介面中描述幾個訂閱方法(需要將發布者作為引數傳入方法中)即可,這樣訂閱者就能在不與具體發布者類耦合的情況下通過介面觀察發布者的狀態

          

  3. 適用性

  • 一個抽象模型有兩個方面,其中一個方面依賴于另一方面,將這兩者封裝在獨立的物件中,以使它們可以各自獨立的改變和復用
  • 對一個物件地改變需要同時改變其它物件,而不知道具體有多少物件有待改變
  • 一個物件必須通知其他物件,而它又不能假定其他物件是誰,換言之,你不希望這些物件是緊密耦合的

  4. 結構

         

  5. 效果

  Observer模式允許你獨立地改變目標和觀察者

  1. 目標和觀察者間地抽象耦合   一個目標所知道的僅僅是它有一系列觀察者,每個都符合抽象的Observer類的簡單介面,目標不知道任何一個觀察者屬于哪個具體的類,這樣目標和觀察者之間地耦合是抽象和最小的,

  2. 支持廣播通信    不像通常的請求,目標發送的通知不需要指定它的接收者,通知被自動廣播給所有已向該目標物件登記的物件,另外,處理還是忽略一個通知取決于觀察者

  3. 意外的更新      由于一個觀察者并不知道其他觀察者地存在,它可能對改變目標的最終代價一無所知

  6. 代碼實作    

  本例中,觀察者模式在文本編輯器的物件之間建立了間接的合作關系,每當編輯器 (Editor)物件改變時,它都會通知其訂閱者, ?郵件通知監聽器 (Email-Notification-Listener)和日志開啟監聽器 (Log-Open-Listener)都將通過執行其基本行為來對這些通知做出反應,
訂閱者類不與編輯器類相耦合,且能在需要時在其他應用中復用, ?編輯器類僅依賴于抽象訂閱者介面,這樣就能允許在不改變編輯器代碼的情況下添加新的訂閱者型別,

  publisher/EventManager.java: 基礎發布者

package observer.publisher;

import observer.listeners.EventListener;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author GaoMing
 * @date 2021/7/26 - 10:01
 */
public class EventManager {
    Map<String, List<EventListener>> listeners = new HashMap<>();

    public EventManager(String... operations) {
        for (String operation : operations) {
            this.listeners.put(operation, new ArrayList<>());
        }
    }

    public void subscribe(String eventType, EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.add(listener);
    }

    public void unsubscribe(String eventType, EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.remove(listener);
    }

    public void notify(String eventType, File file) {
        List<EventListener> users = listeners.get(eventType);
        for (EventListener listener : users) {
            listener.update(eventType, file);
        }
    }
}

  editor/Editor.java: 具體發布者,由其他物件追蹤

package observer.editor;

import observer.publisher.EventManager;

import java.io.File;

/**
 * @author GaoMing
 * @date 2021/7/26 - 10:01
 */
public class Editor {
    public EventManager events;
    private File file;

    public Editor() {
        this.events = new EventManager("open", "save");
    }

    public void openFile(String filePath) {
        this.file = new File(filePath);
        events.notify("open", file);
    }

    public void saveFile() throws Exception {
        if (this.file != null) {
            events.notify("save", file);
        } else {
            throw new Exception("Please open a file first.");
        }
    }
}

  listeners/EventListener.java: 通用觀察者介面

package observer.listeners;

import java.io.File;

/**
 * @author GaoMing
 * @date 2021/7/26 - 10:02
 */
public interface EventListener {
    void update(String eventType, File file);
}

  listeners/EmailNotificationListener.java: 收到通知后發送郵件

package observer.listeners;

import java.io.File;

/**
 * @author GaoMing
 * @date 2021/7/26 - 10:02
 */
public class EmailNotificationListener implements EventListener{
    private String email;

    public EmailNotificationListener(String email) {
        this.email = email;
    }

    @Override
    public void update(String eventType, File file) {
        System.out.println("Email to " + email + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
    }
}

  listeners/LogOpenListener.java: 收到通知后在日志中記錄一條訊息

package observer.listeners;

import java.io.File;

/**
 * @author GaoMing
 * @date 2021/7/26 - 10:03
 */
public class LogOpenListener implements EventListener{
    private File log;

    public LogOpenListener(String fileName) {
        this.log = new File(fileName);
    }

    @Override
    public void update(String eventType, File file) {
        System.out.println("Save to log " + log + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
    }

}

  Demo.java: 客戶端代碼

package observer;

import observer.editor.Editor;
import observer.listeners.EmailNotificationListener;
import observer.listeners.LogOpenListener;

/**
 * @author GaoMing
 * @date 2021/7/26 - 10:00
 */
public class Demo {
    public static void main(String[] args) {
        Editor editor = new Editor();
        editor.events.subscribe("open", new LogOpenListener("/path/to/log/file.txt"));
        editor.events.subscribe("save", new EmailNotificationListener("[email protected]"));

        try {
            editor.openFile("test.txt");
            editor.saveFile();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  運行結果

Save to log \path\to\log\file.txt: Someone has performed open operation with the following file: test.txt
Email to [email protected]: Someone has performed save operation with the following file: test.txt

    

  7. 實作

  1)創建目標到其觀察者之間的映射     一個目標物件跟蹤它應通知的觀察者的最簡單的方法是顯式地在目標中保存對它們地參考,然而,當目標很多而觀察者較少時,這樣存盤可能代價太高,一個解決辦法是用時間換空間,用一個關聯查找機制(例如一個hash表)來維護目標到觀察者地映射,這樣一個沒有觀察者的目標就不產生存盤開銷,但另一方面,這一方法增加了訪問觀察者的開銷

  2)觀察多個目標    有時候,一個觀察者依賴于多個目標,例如,一個表格物件可能依賴于多個資料源,在這種情況下,必須擴展update介面,目標物件可以簡單的將自己作為Update操作地一個引數,讓觀察者知道應該去檢查哪個目標

  3)誰觸發更新     目標和它的觀察者依賴于通知機制來保持一致,但到底哪個物件呼叫Notify來觸發更新? 這里有兩個選擇:

  • 由目標物件的狀態設定操作在改變目標物件的狀態后自動呼叫Notify,這種方法的優點是客戶不需要記住要在目標物件上呼叫Notify,缺點是多個連續的操作會產生多次連續的更新,可能效率較低
  • 讓客戶負責在合適的時候呼叫notify,這樣做的優點是客戶在一系列狀態改變完成后一次性的觸發更新,避免了不必要的中間更新,缺點是給客戶增加了觸發更新的責任,由于客戶可能會忘記呼叫Notify,這種方式交易出錯

  4)在發出通知前確保目標的狀態自身是一致的       在發出通知前確保狀態自身一致這一點很重要,因為觀察者在更新其狀態的程序中需要查詢目標的當前狀態,可以使用模板方法發送通知來避免這種錯誤,定義那些子類可以重定義的原語操作,并將Notify作為模板方法中的最后一個操作,這樣當子類重定義Subject的操作時,還可以保證該物件的狀態是自身一致的,另外,最好在檔案中注明哪個Subject操作觸發通知

  5)避免特定于觀察者的更新協議——推/拉模型     觀察者模式的實作經常需要讓目標廣播關于其改變的其他一些資訊,目標將這些資訊作為Update操作的一個引數傳遞出去,一個極端情況是,目標向觀察者發送關于改變的詳細資訊,而不管它們需要與否,即推模型,另一個極端是拉模型,目標除最小通知外什么也不送出,而在此之后由觀察者顯式的向目標詢問細節,拉模型強調的是目標不知道它的觀察者,而推模型假定目標知道一些觀察者需要的資訊,推模型使得觀察者相對難以復用,另一方面,拉模型效率會較差,因為觀察者物件需要在沒有目標物件的幫助下,確定什么改變了

  6)顯式地指定感興趣的改變       可以通過擴展目標的注冊介面,讓觀察者注冊為僅對特定事件感興趣的觀察者,如上面例子中,將EventType作為引數傳遞給Notify 和Update方法

  7)封裝復雜的更新語意        當目標和觀察者間的依賴關系特別復雜時,可能需要一個維護這些關系的物件,即ChangeManager(更改管理器),其目的是盡量減少觀察者反映其目標狀態變化所需的作業量,例如,如果一個操作涉及幾個相互依賴的目標進行改動,就必須保證在所有的目標更新完畢后,才一次性的通知它們的觀察者,而不是每個目標都通知觀察者,ChangeManager是一個Mediator模式的實體,相比于,SimpleChangeManager,當一個觀察者觀察多個目標時,DAGChangeManager保證觀察者僅接收一個更新

                   

  8. 與其他模式的關系

  • 責任鏈模式、命令模式、中介者模式和觀察者模式用于處理請求發送者和接收者之間的不同連接方式:
    責任鏈按照順序將請求動態傳遞給一系列的潛在接收者,直至其中一名接收者對請求進行處理
    命令在發送者和請求者之間建立單向連接
    中介者清除了發送者和請求者之間的直接連接,強制它們通過一個中介物件進行間接溝通
    觀察者允許接收者動態地訂閱或取消接收請求

  • 中介者和觀察者有的時候會非常相似
    中介者的主要目標是消除一系列系統組件之間的相互依賴,這些組件將依賴于同一個中介者物件,觀察者的目標是在物件之間建立動態的單向連接,使得部分物件可作為其他物件的附屬發揮作用
    有一種流行的中介者模式實作方式依賴于觀察者,中介者物件擔當發布者的角色,其他組件則作為訂閱者,可以訂閱中介者的事件或取消訂閱,當中介者以這種方式實作時,它可能看上去與觀察者非常相似

  9. 已知應用  

  觀察者模式在 Java 代碼中很常見,特別是在 GUI 組件中,它提供了在不與其他物件所屬類耦合的情況下對其事件做出反應的方式
  這里是核心 Java 程式庫中該模式的一些示例:
  java.util.Observer/ java.util.Observable (極少在真實世界中使用)
  java.util.EventListener的所有實作 (幾乎廣泛存在于 Swing 組件中)
  javax.servlet.http.HttpSessionBindingListener
  javax.servlet.http.HttpSessionAttributeListener
  javax.faces.event.PhaseListener
  識別方法: 該模式可以通過將物件存盤在串列中的訂閱方法, 和對于面向該串列中物件的更新方法的呼叫來識別

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/308452.html

標籤:設計模式

上一篇:狀態模式(學習筆記)

下一篇:訪問者模式(學習筆記)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more