設計模式-觀察者模式(Observer)

来源:https://www.cnblogs.com/sayook/archive/2020/04/23/12759319.html
-Advertisement-
Play Games

講故事(user story) 假設我們是一個優惠券提供平臺,故事就發生在顧客在我們平臺採購完成支付成功後。 支付完成後平臺要進行的一些操作: 1. 簡訊通知客戶已經生成訂單 2. 增加顧客的積分 3. 開始按訂單需求制券 ​ 。。。(可能會有許多操作) 接下來就是將故事以代碼的形式展現出來。。。 ...


講故事(user story)

假設我們是一個優惠券提供平臺,故事就發生在顧客在我們平臺採購完成支付成功後。

支付完成後平臺要進行的一些操作:

  1. 簡訊通知客戶已經生成訂單

  2. 增加顧客的積分

  3. 開始按訂單需求制券

​ 。。。(可能會有許多操作)

接下來就是將故事以代碼的形式展現出來。。。

需求分析

我們將上述故事轉化為代碼中的對象分別是: 支付成功 PaySuccessSubject、簡訊通知 MessageObserver、積分增加 BonusObserver、制券 CouponObserver

當支付成功PaySuccessSubject後,要通知到MessageObserverBonusObserverCouponObserver這三個對象,為了實現上面這個需求,將採用觀察者模式(發佈-訂閱)

敲黑板.劃重點

觀察者模式又叫發佈-訂閱(Publish/Subscribe)模式,定義了一種一對多的依賴關係,讓多個觀察者對象同時 監聽某一個主題對象。當主題對象在狀態發生變化時,通知所有觀察者對象,使他們能夠自己更新自己。

觀察者模式結構圖

Show Code

Subject類,把所有觀察者對象的引用保存在一個集合了,每個通知者都可以有任何數量的觀察者。抽象通知者提供 可以增加和刪除觀察者對象的介面。

/// <summary>
/// 抽象通知者
/// </summary>
public abstract class Subject
{
    /// <summary>
    /// 觀察者集合
    /// </summary>
    protected List<IObserver> observers = new List<IObserver>();

    public string State { get; set; }

    /// <summary>
    /// 添加觀察者
    /// </summary>
    /// <param name="observer">觀察者</param>
    public void Attach(IObserver observer)
    {
        observers.Add(observer);
    }

    /// <summary>
    /// 刪除觀察者
    /// </summary>
    /// <param name="observer">觀察者</param>
    public void Detach(IObserver observer)
    {
        observers.Remove(observer);
    }

    /// <summary>
    /// 通知
    /// </summary>
    /// <returns></returns>
    public void Notify()
    {
        foreach (var observer in observers)
        {
            observer.Update();
        }
    }
}

PaySuccessSubject類,具體的通知者,給所有登記過的觀察者發出通知。

 /// <summary>
 /// 支持成功通知者
 /// </summary>
 public class PaySuccessSubject : Subject
 {
 }

Observer類,抽象觀察者,為所有的具體的觀察者定義一個介面,一般用抽象類或介面實現。通常包含一個Update()更新方法。

 /// <summary>
 /// 抽象觀察
 /// </summary>
 public abstract class Observer
 {
     public abstract void Update();
 }

MessageObserverBonusObserverCouponObserver具體的觀察者類,實現更新介面,以便本身的狀態與主題的狀態相協調。

    /// <summary>
    /// 簡訊觀察者
    /// </summary>
    public class MessageObserver : Observer
    {
        public Subject Subject { get; set; }

        public MessageObserver(Subject subject)
        {
            Subject = subject;
        }

        public override void Update()
        {
            Console.WriteLine($"{Subject.State}:簡訊通知了...");
        }
    }
    /// <summary>
    /// 積分觀察者
    /// </summary>
    public class BonusObserver : Observer
    {
        public Subject Subject { get; set; }

        public BonusObserver(Subject subject)
        {
            Subject = subject;
        }

        public override void Update()
        {
            Console.WriteLine($"{Subject.State}:積分增加了...");
        }
    }

    /// <summary>
    /// 券觀察者
    /// </summary>
    public class CouponObserver : Observer
    {
        public Subject Subject { get; set; }

        public CouponObserver(Subject subject)
        {
            Subject = subject;
        }
        public override void Update()
        {
            Console.WriteLine($"{Subject.State}:開始制券了...");
        }
    }

客戶端代碼

        private static void Main(string[] args)
        {
            var subject = new PaySuccessSubject();
            var observer1 = new CouponObserver(subject);
            var observer2 = new MessageObserver(subject);
            var observer3 = new BonusObserver(subject);

            //添加訂閱
            subject.Attach(observer1);
            subject.Attach(observer2);
            subject.Attach(observer3);

            //發佈通知
            subject.State = "星巴克10十元券採購成功";
            subject.Notify();

            Console.WriteLine("\n\nHappy Ending~");
            Console.ReadLine();
        }

結果顯示
result2

Code Upgrade

code review後發現,在通知給觀察者時,是順序執行,如果其中一個觀察者卡頓或者錯誤,會導致其他觀察者卡克,所以我們應該採用非同步方式。

下麵我們將模擬 制券過程耗時增加,但不影響通知其他觀察者。直接上代碼:

    /// <summary>
    /// 抽象觀察
    /// </summary>
    public abstract class Observer
    {
        public abstract Task UpdateAsync();
    }

    /// <summary>
    /// 簡訊觀察者
    /// </summary>
    public class MessageObserver : Observer
    {
        public Subject Subject { get; set; }

        public MessageObserver(Subject subject)
        {
            Subject = subject;
        }

        public override Task UpdateAsync()
        {
            Console.WriteLine($"{Subject.State}:簡訊通知了...");
            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// 積分觀察者
    /// </summary>
    public class BonusObserver : Observer
    {
        public Subject Subject { get; set; }

        public BonusObserver(Subject subject)
        {
            Subject = subject;
        }

        public override Task UpdateAsync()
        {
            Console.WriteLine($"{Subject.State}:積分增加了...");
            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// 券觀察者
    /// </summary>
    public class CouponObserver : Observer
    {
        public Subject Subject { get; set; }

        public CouponObserver(Subject subject)
        {
            Subject = subject;
        }

        public override async Task UpdateAsync()
        {
            Console.WriteLine($"{Subject.State}:開始制券...");
            //模擬制券耗時
            await Task.Delay(3000);
            Console.WriteLine($"{Subject.State}:制券完成...");
        }
    }

    /// <summary>
    /// 抽象通知者
    /// </summary>
    public abstract class Subject
    {
        /// <summary>
        /// 觀察者集合
        /// </summary>
        protected List<Observer> observers = new List<Observer>();

        public string State { get; set; }

        /// <summary>
        /// 添加觀察者
        /// </summary>
        /// <param name="observer">觀察者</param>
        public void Attach(Observer observer)
        {
            observers.Add(observer);
        }

        /// <summary>
        /// 刪除觀察者
        /// </summary>
        /// <param name="observer">觀察者</param>
        public void Detach(Observer observer)
        {
            observers.Remove(observer);
        }

        /// <summary>
        /// 通知
        /// </summary>
        /// <returns></returns>
        public Task Notify()
        {
            foreach (var observer in observers)
            {
                observer.UpdateAsync();
            }
            return Task.CompletedTask;
        }
    }

客戶端端代碼:

        private static async Task Main(string[] args)
        {
            var subject = new PaySuccessSubject();
            var observer1 = new CouponObserver(subject);
            var observer2 = new MessageObserver(subject);
            var observer3 = new BonusObserver(subject);

            //添加訂閱
            subject.Attach(observer1);
            subject.Attach(observer2);
            subject.Attach(observer3);

            //發佈通知
            subject.State = "星巴克10十元券採購成功";
            await subject.Notify();

            Console.WriteLine("\n\nHappy Ending~");
            Console.ReadLine();
        }

結果顯示:

result

委托加持觀察者模式

現實開發中,很多觀察者對象共同繼承或者實現同一個抽象觀察者,不合適;並且所有觀察者對象的操作方法統一叫一個 Update(),達不到望文生義的效果,所以我們對觀察者模式再次進行升級,使用委托來替換掉抽象觀察者,

直接上代碼:

    /// <summary>
    /// 簡訊觀察者
    /// </summary>
    public class MessageObserver
    {
        public ISubject Subject { get; set; }

        public MessageObserver(ISubject subject)
        {
            Subject = subject;
        }

        /// <summary>
        /// 發送簡訊
        /// </summary>
        /// <returns></returns>
        public Task SendMessageAsync()
        {
            Console.WriteLine($"{Subject.State}:簡訊通知了...");
            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// 積分觀察者
    /// </summary>
    public class BonusObserver
    {
        public ISubject Subject { get; set; }

        public BonusObserver(ISubject subject)
        {
            Subject = subject;
        }

        /// <summary>
        /// 添加積分
        /// </summary>
        /// <returns></returns>
        public Task AddBonusAsync()
        {
            Console.WriteLine($"{Subject.State}:積分增加了...");
            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// 券觀察者
    /// </summary>
    public class CouponObserver
    {
        public ISubject Subject { get; set; }

        public CouponObserver(ISubject subject)
        {
            Subject = subject;
        }

        /// <summary>
        /// 制券
        /// </summary>
        /// <returns></returns>
        public async Task MakeCouponAsync()
        {
            Console.WriteLine($"{Subject.State}:開始制券...");
            //模擬制券耗時
            await Task.Delay(3000);
            Console.WriteLine($"{Subject.State}:制券完成...");
        }
    }

    /// <summary>
    /// 抽象通知者
    /// </summary>
    public interface ISubject
    {
        /// <summary>
        /// 通知
        /// </summary>
        /// <returns></returns>
        public Task Notify();

        public string State { get; set; }
    }

    /// <summary>
    /// 支持成功通知者
    /// </summary>
    public class PaySuccessSubject : ISubject
    {
        public Func<Task> Update;

        public string State { get; set; }

        public Task Notify()
        {
            Update();
            return Task.CompletedTask;
        }
    }
}

客戶端調用:

    internal class Program
    {
        private static async Task Main(string[] args)
        {
            var subject = new ObserverDelegate.PaySuccessSubject();
            var observer1 = new ObserverDelegate.CouponObserver(subject);
            var observer2 = new ObserverDelegate.MessageObserver(subject);
            var observer3 = new ObserverDelegate.BonusObserver(subject);
            //添加訂閱
            subject.Update += observer1.MakeCouponAsync;
            subject.Update += observer2.SendMessageAsync;
            subject.Update += observer3.AddBonusAsync;
            //發佈通知
            subject.State = "星巴克10十元券採購成功";
            await subject.Notify();

            Console.WriteLine("\n\nHappy Ending~");
            Console.ReadLine();
        }
    }
}

展示結果和上面一樣。

源碼地址:https://gitee.com/sayook/DesignMode/tree/master/Observer


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • [TOC] 1.製作docker鏡像的步驟 2.製作支持ssh遠程登陸的docker鏡像 2.1.啟動容器安裝軟體服務 2.2.將安裝好服務的容器commit提交為鏡像 2.3.啟動新容器來測試新提交的鏡像 3.製作支持ssh+httpd雙服務的鏡像 3.1.啟動容器安裝軟體服務 3.2.將安裝好服 ...
  • [TOC] 1.為什麼要使用docker數據捲 2.常見的docker數據捲命令 3.docker數據捲運用一 4.把宿主機的目錄掛載到容器中 ...
  • 安裝 Docker 請參考我的另一篇文章 "Docker 安裝及使用" 建立鏡像 拉取鏡像 檢查拉取是否成功 創建資料庫容器(不建立數據映射) 創建資料庫容器(建立數據映射) 檢查容器是否正確運行 遠程連接MySQL 進入docker本地連接mysql客戶端 遠程連接mysql 遠程連接軟體時要註意 ...
  • abc 過濾器介紹 HBase過濾器是一套為完成一些較高級的需求所提供的API介面。 過濾器也被稱為下推判斷器(push down predicates),支持把數據過濾標準從客戶端下推到伺服器,帶有 Filter 條件的 RPC 查詢請求會把 Filter 分發到各個 RegionServer,所 ...
  • 添加以下配置 // 重啟mysql 查看是否開啟 ; 查看日誌狀態 ; 刷新日誌,刷新之後會新建一個新的Binlog日誌 清空目錄下所有日誌文件 查看日誌文件: bash / !50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1 /; / !50003 SET @OLD_ ...
  • 1. Java 下載Java 1.8 64位版本https://java.com/en/download/. 如果不是64位java,在啟動資源節點管理器時會出現錯誤 Java 安裝自選目錄, 必須是中間不帶空格的目錄結構:比如 C:\java64 安裝完成後,使用“java -version”命令 ...
  • 1.mysql登陸 完整登陸命令: mysql -u root -p xxxxx -h 127.0.0.1 -P 23306 語法:mysql -u 用戶名 -p 密碼 -h mysql伺服器的IP地址 -P 使用的埠號 非完整登陸命令: mysql -u root -p 回車(回車後再輸入密碼) ...
  • 定義 LOCATE 查找一個子串在另一個字元串是否存在 語法 網上各種文章都說有兩種語法,其實只不過第三個參數可選,且預設為1而已,何必搞這麼複雜。 翻看了一下手冊,12.7小節里確實也是兩種,不知道為什麼。 返回 substr 在 str 從 pos 開始第一次出現的位置,索引從1開始,不存在則返 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...