主頁 > 軟體設計 > 設計模式-觀察者模式下

設計模式-觀察者模式下

2021-06-09 19:26:13 軟體設計

上一篇說到了觀察者模式較為傳統的用法,這篇準備分享點流行的,不過在開始新內容之前,我們不妨先思考一下兩種場景,一個是報社訂閱報紙,另一個是在黑板上發公告,都是典型觀察者模式應用場景,二者有何不同?

  1. 報社訂閱報紙,訂閱者需要到報社登記交錢,然后報社才會每次有新報紙時通知到訂閱者,
  2. 而在黑板上發公告,發布的人不知道誰會看到,看到的人也不知道是誰發出的,而事實上,看到公告的人也可能只是偶然的機會瞟了一眼黑板而已,

可以看到,二者有明顯的區別,前者,觀察者必須要注冊到被觀察者上才能接收通知;而后者,觀察者和被觀察者之間是相互完全陌生的,回顧一下我們在上一篇中舉的例子,不難發現它其實類似第二種場景,狗叫并不知道誰會聽見,而聽的人也不是為了聽狗叫,他僅僅是在關注外界的動靜,恰好聽到了狗叫而已,但我們采用的是類似第一種場景的處理方式,顯然并不合適,因此,也就自然而然的留下了兩個問題:

  1. dog.AddObserver(...)真的合適嗎?實際生活中,狗真的有這種能力嗎?
  2. 我們知道C#中不支持多繼承,如果Dog本身繼承自Animal的基類,如果同時作為被觀察者,除了用上述演進一的實作,還能如何實作?

針對這兩個問題,該怎么解決了?不妨再回顧一下之前學過的設計原則,看看哪里可以尋找突破口,

一番思索不難發現,主題類違背了合成復用原則,也就是我們常說的,HAS AIS A更好,既然知道HAS A更好,我們為什么非得通過繼承來實作功能的復用呢?更何況我們繼承的還是個普通類,

演進四-事件總線

基于這種思路,我們可以試著把繼承改成組合,不過在這之前,我們不妨一步到位,干脆再為Subject類定義一個抽象的介面,免得看著不舒服,畢竟面向抽象編程嘛:

public interface ISubject
{
    void AddObserver(IObserver observer);

    void RemoveObserver(IObserver observer);

    void Publish(EventData eventData);
}

public class Subject: ISubject
{
    private readonly IList<IObserver> _observers = new List<IObserver>();

    public void AddObserver(IObserver observer)
    {
        _observers.Add(observer);
    }

    public void RemoveObserver(IObserver observer)
    {
        _observers.Remove(observer);
    }

    public void Publish(EventData eventData)
    {
        foreach (var observer in _observers)
        {
            observer.Update(eventData);
        }
    }
}

邏輯并沒有任何改動,僅僅是實作了一個介面而已,這一步不做其實也沒有關系,接下來該做什么應該也很清楚了,沒錯,就是組合到被觀察者中去,也就是DogSon,下面是具體的實作:

public class Dog
{
    private readonly ISubject _subject;

    public Dog(ISubject subject)
    {
        this._subject = subject;
    }

    public void Bark()
    {
        Console.WriteLine("遙聞深巷中犬吠");

        _subject.Publish(new EventData { Source = this, EventType = "DogBark" });
    }
}

public class Son : IObserver
{
    private readonly ISubject _subject;
    public Son(ISubject subject)
    {
        this._subject = subject;
    }
    public void Update(EventData eventData)
    {
        if (eventData.EventType == "DogBark")
        {
            Wakeup();
        }
    }

    public void Wakeup()
    {
        Console.WriteLine("既而兒醒,大啼");
        _subject.Publish(new EventData { Source = this, EventType = "SonCry" });
    }
}

修改的僅僅是被觀察者,觀察者不需要做任何改變,看到上面的呼叫,不知道大家有沒有一種熟悉的感覺呢?沒錯,這里的使用方式像極了微服務中常用的事件總線EventBus,事實上,事件總線就是這么實作的,基本原理僅僅是觀察者模式繼承組合而已,

再看看呼叫的地方:

static void Main(string[] args)
{
    ISubject subject = new Subject();
    Dog dog = new Dog(subject);
    Wife wife = new Wife();
    Husband husband = new Husband();
    Son son = new Son(subject);

    subject.AddObserver(wife);
    subject.AddObserver(husband);
    subject.AddObserver(son);

    dog.Bark();
}

DogSubject之間的關系改為HAS A之后,實際的事件發出者和事件接收者之間多了一層,使得二者之間完全解耦了,這時,Dog可以繼承自己的Animal基類了,并且也不用再做類似在Dog類中管理WifeHusbandSon這么奇怪的事了,對觀察者的管理交給總線來完成,

再來看看這時的類圖長什么樣子:

如果覺得復雜,可以不看DogSun這兩個節點,只看實線框中的部分,有沒有發現就是前面簡易版的觀察者模式呢?被觀察者還是Subject,只不過和DogSun已經沒什么關系了,這是多一層必然會導致的結果,到這里,其實已經完美實作需求了,Subject是原來的被觀察者,但現在相當于事件總線,在程式啟動的時候,將觀察者全部注冊到總線上就可以接收到總線上的事件訊息了,

演進五-MQ

你以為這樣就完了嗎?其實并沒有,再回到軟體開發領域,我們知道,事件的觸發可以發生在系統內部,也可以發生在系統之間,而前面無論哪種方式的實作,其實解決的都是內部問題,那如果需要跨系統該怎么辦呢?直接呼叫的話,會像上篇當中的第一個實作一樣,出現強耦合,只不過這時呼叫的不再是普通的方法,而是跨網路的API,而強耦合的也不再是類與類之間,而是系統與系統之間,并且隨著事件數量的增多,也會使得呼叫鏈變得混亂不堪,難以管理,

為了解決這個問題,就需要在所有系統之外,加入一個中間代理的角色,所有發布者將事件訊息按不同主題發送給代理,然后代理再根據觀察者關注主題的不同,將訊息分發給相應的觀察者,當然,前提是發布者和觀察者都提前在代理這里完成注冊登記,

我們先模擬實作一個代理,當然,我這里只是通過單例模式實作一個簡單的示例,真實情況會比這個復雜的多:

public class Broker
{
    private static readonly Lazy<Broker> _instance
        = new Lazy<Broker>(() => new Broker());

    private readonly Queue<EventData> _eventDatas = new Queue<EventData>();

    private readonly IList<IObserver> _observers = new List<IObserver>();

    private readonly Thread _thread;
    private Broker()
    {
        _thread = new Thread(Notify);
        _thread.Start();
    }

    public static Broker Instance
    {
        get
        {
            return _instance.Value;
        }
    }

    public void AddObserver(IObserver observer)
    {
        _observers.Add(observer);
    }

    public void RemoveObserver(IObserver observer)
    {
        _observers.Remove(observer);
    }

    private void Notify(object? state)
    {
        while (true)
        {
            if (_eventDatas.Count > 0)
            {
               var eventData = https://www.cnblogs.com/FindTheWay/p/_eventDatas.Dequeue();
                foreach (var observer in _observers)
                {
                    observer.Update(eventData);
                }
            }

            Thread.Sleep(1000);
        }
    }

    public void Enqueue(EventData eventData)
    {
        _eventDatas.Enqueue(eventData);
    }
}

這里通過單例模式定義了一個Broker代理類,實際情況下,這部分是由一個永不停機的MQ服務承擔,主要包括四個部分組成:

  1. 一個Queue<EventData>型別的佇列,用于存放事件訊息;
  2. 一組注冊和注銷觀察者的方法;
  3. 一個接收來自事件發布者的事件訊息的方法;
  4. 最后就是事件訊息的通知機制,這里用的是定時輪詢的方式,實際應用中肯定不會這么簡單,

事實上,上述四個部分都應該針對不同的主題實作,也就是我們常常會提到的Topic,幾乎所有的MQ都會有Topic的概念,為了簡單,我們這里就不考慮了,

再來看看Subject的實作:

public interface ISubject
{
    void Publish(EventData eventData);
}

public class Subject: ISubject
{
    public void Publish(EventData eventData)
    {
        Broker.Instance.Enqueue(eventData);
    }
}

由于對IObserver的管理交給了Broker代理,因此這里就不需要再關注具體的觀察者是誰,也不需要管理觀察者了,只需要負責發布事件就行了,需要注意的是,事件訊息發布給了Broker,后續的一切作業交給Broker全權處理,觀察者依然不需要做任何代碼上的修改,

呼叫的地方涉及到的改變主要體現在觀察者的注冊上,畢竟管理者不再是Subject,而是交由Broker代理接管了:

static void Main(string[] args)
{
    ISubject subject = new Subject();
    Dog dog = new Dog(subject);
    Wife wife = new Wife();
    Husband husband = new Husband();
    Son son = new Son(subject);
    Broker.Instance.AddObserver(wife);
    Broker.Instance.AddObserver(husband);
    Broker.Instance.AddObserver(son);

    dog.Bark();
}

乍一看,事情變得越來越復雜了,這里為了解決跨系統的問題,又套了一層,類圖有點復雜,為避免混亂,我就不畫了,不過好在思路的演進是清晰的,達到現在的結果,應該也不會覺得突兀,這個其實就是當前盛行的MQ的基本實作思路了,

演程序序

通過前面一系列的改造,我們解決了不同場景下的事件處理問題,接下來,我們再次梳理一下觀察者模式的整個演程序序,先看一張圖:

這張圖顯示了觀察者模式演進的不同階段,主題與觀察者之間的呼叫關系:

  1. 第一階段降低了主題與觀察者之間的耦合度,但并沒有完全解耦,這種情況主要應用在類似報紙訂閱的場景;
  2. 第二階段在主題與觀察者之間加了一條總線,使得主題與觀察者完全解耦,這種情況主要運用在類似黑板發布公告的場景,但該實作難以應對跨系統的事件處理;
  3. 第三階段在總線與觀察者之間又加了一個代理,使得存在于不同系統之間的主題與觀察者也能夠解耦并且正常通信了,

可以看出,他們都有各自的應用場景,并不能簡單的說誰更先進,誰能替代誰,可以預見,觀察者模式未來可能還會繼續演進,去應對更多新的更復雜的場景,

.Net中的應用

既然觀察者模式這么好用,那.Net框架中自然也會內置一些處理機制了,

  1. 在.Net專案中,委托(delegate)和事件(event)就是觀察者模式的很好的一種實踐,不過需要注意的是,委托和事件,嚴格意義上講,已經不能稱之為設計模式了,因為它們針對的都是方法,跟面向物件設計無關,不過倒是可以稱之為慣用法,不過不管怎么樣,它們要解決的問題跟觀察者模式是一致的,
  2. .Net中提供了一組泛型介面IObserver<T>IObservable<T>可用于實作事件通知機制,顧名思義,前者相當于觀察者,后者相當于主題,

這里就不列代碼,以免喧賓奪主了,因為這不是本文的重點,而且前者太常用了,應該沒什么人不會,而后者呢,不知道大家用的多不多,但其實我自己沒怎么用,我更愿意根據不同的場景來定義語意更明確的介面,如ISender用于發送,IProducer用于生產,IListener用于監聽,IConsumer用于消費等,

總結

事件無處不在,毫不夸張的說,整個世界的運轉都是由事件驅動的,因此觀察者模式也是無處不在的,我們知道,設計模式經過這么多年的發展,已經有了很大的變化,有的下沉變成了某些語言的慣用法,例如后面會講到的迭代器模式,有些上升更偏向于架構模式,例如前面講過的外觀模式,甚至有的被淘汰,例如備忘錄模式,但是觀察者模式卻是唯一一個向上可用于架構設計,向下被實作為慣用法,中間還能重構代碼,簡直無處不在,無所不能,并且可以預見,未來也必然是經久不衰,

說的有點夸張了,不過也確實說明觀察者模式再怎么重視也不為過了!

原始碼鏈接

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

標籤:設計模式

上一篇:歷年高考專業錄取分數線 API 介面

下一篇:責任鏈模式的實踐

標籤雲
其他(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