使用C# (.NET Core) 實現觀察者模式 (Observer Pattern) 並介紹 delegate 和 event


觀察者模式

這里面綜合了幾本書的資料.

需求

有這么個項目: 

需求是這樣的:

一個氣象站, 有三個傳感器(溫度, 濕度, 氣壓), 有一個WeatherData對象, 它能從氣象站獲得這三個數據. 還有三種設備, 可以按要求展示氣象站的最新數據.

WeatherData的結構如下:

有3個get方法, 分別獲取最新的氣溫, 濕度和氣壓. 還有一個measurementsChanged()方法, 當任一傳感器有變化的時候, 這個方法都會被調用.

總結一下項目的需求:

  • WeatherData類有三個get方法可以獲取溫度, 濕度和氣壓
  • 如果任何一個數據發生變化, 那么measureChanged()方法就會被調用
  • 我們需要實現這三種顯示設備:
    •   當前天氣
    •   數據統計
    •   天氣預測
  • 系統必須可以擴展, 其他開發者可以創建自定義展示設備.

初版代碼

這個地方有個"錯誤", xxxDisplay都是具體的實現, 而編程規則要求是應該對接口編程而不是對實現編程.

那么什么是觀察者模式?

舉一個例子:

  1. 報社發行報紙
  2. 你訂閱報紙, 一旦有新一期的報紙發行, 新報紙就會送到你家里, 只要你一直訂閱, 你就一直會收到新報紙
  3. 你不再訂閱報紙的時候, 就收不到以后的新報紙了
  4. 報社運營的時候, 一直會有人去訂閱或者取消訂閱報紙.

發布者 + 訂閱者 = 觀察者模式
Publishers + Subscribers = Observer Pattern
在觀察者模式里, 我們把報社叫做被觀察對象(Subject), 把訂閱者叫做觀察者(Observers)

觀察者模式是這樣操作的:

  

觀察者模式的定義就是:

一個目標物件管理所有相依於它的觀察者物件,並且在它本身的狀態改變時主動發出通知

類圖如下:

 

談一下松耦合

當兩個對象是松耦合的時候, 他們可以進行交互, 但是卻幾乎不了解對方.
觀察者模式下的被觀察者(Subject)和觀察者(Observers)就是松耦合設計的對象. 這是因為:

  • 被觀察者(Subject)只知道觀察者實現了某個接口
  • 可以隨時添加觀察者
  • 添加新類型觀察者的時候不需要修改被觀察者
  • 可以復用觀察者或者被觀察者
  • 如果被觀察者或觀察者發生變化了, 那么這些變化不會影響到對方.

一個設計原則:

交互的對象之間應盡量設計成松耦合的. Strive for loosely coupled designs between objects that interact.
松耦合設計可以讓我們設計出這樣的系統: 因為對象之間的相互依存減小了, 所以系統可以輕松處理變化.

重新設計:

代碼:

OK, 上面是書中的內容, C#7.0里面對觀察者模式是怎么實現的呢?

先只談下面這個:

Event

談到Event, 就得把delegate先細說一下

Delegate 委托

一個委托類型定義了某種類型的方法(方法的返回類型和參數類型), 然后這個委托的實例可以調用這些方法.

例如:

delegate int Transformer (int x);

這個委托就和返回類型是int, 參數是一個int的方法兼容.

例如:

static int Square (int x) { return x * x };
//
static int Square (int x) => x * x;

 

把一個方法賦值給委托變量的時候就創建了一個委托的實例:

Transformer t = Square;

 

然后就可以像方法一樣進行調用:

int answer = t(3); // 9

 

所以說一個委托的實例就是調用者的委托: 調用者調用委托, 然后委托調用目標方法, 這樣就把調用者和目標方法解耦了.

其中:

Transformer t = Square;
// 是下面的簡寫
Transformer t = new Transformer(Square);

 

t(3)
// 是下面的簡寫
t.Invoke(3)

 

多播委托

一個委托實例可以引用多個目標方法. 使用+=操作符.

SomeDelegate d = Method1;
d += Method2;
// 第二行相當於:
d = d + Method2;

 

調用d的時候就會調用Method1和Method2兩個方法.

委托方法的調用順序和它們被添加的順序是一樣的.

使用-=操作符來移除目標方法:

d -= Method1;

 

這時調用d后只會執行Method2了.

注意: 委托是不可變的 +=/-=實際上是創建了新的委托.

多播委托返回類型

如果多播委托有返回值(非void), 那么調用者只會獲得最后一個被調用方法的返回值.

委托也可以使用泛型:

public delegate T Transformer<T> (T arg);

 

Func 和 Action

記住Func有返回值, Action沒有就行.

 

Event

使用委托的時候, 通常會有兩個角色出現: 廣播者(被觀察者)和訂閱者(觀察者) [觀察者模式]

廣播者包含一個委托field, 廣播者決定何時廣播, 它通過調用委托進行廣播.

訂閱者就是方法的目標接收者.訂閱者可以決定何時開始和結束監聽, 是通過在廣播者的委托上使用+=和-=操作符來實現的.

訂閱者之間互相不了解, 不干擾.

event就是為上述模型所存在的, 它只把上述模型所必須的功能從委托里暴露出來. 它的主要目的就是防止訂閱者之間相互干擾.

最簡單聲明event的方法就是在委托成員前面加上event關鍵字:

public delegate void SomeChangedHandler(decimal x);

public class Broadcaster
{
    public event SomeChangedHandler handler;
}

 

在Broadcaster類里面的代碼, 可以把handler作為委托一樣來用.

在Broadcaster類外邊, 只能對這個event執行+=和-=操作.

 

Event 模式/ 觀察者模式

這種模式在.net core里首先需要EventArgs.

EventArgs是一個基類, 它可以為event傳遞信息.

可以創造它的子類來傳遞自定義參數:

public class FallsIllEventArgs : EventArgs
    {
        public readonly string Address;

        public FallsIllEventArgs(string address)
        {
            this.Address = address;
        }
    }

 

然后就需要給這個event定義一個委托了, 這有三條規則:

  • 返回類型必須是void
  • 需要有兩個參數, 第一個是object, 第二個是EventArgs的子類. 第一個參數代表着廣播者, 第二個參數包含額外的需要傳遞的信息.
  • 名稱必須以EventHandler結束.

.net core定義了System.EventHandler<>, 它滿足這些要求.

public event EventHandler<FallsIllEventArgs> FallsIll;

 

最后, 需要寫一個 protected virtual 方法可以觸發event. 方法的名稱必須和event匹配: 以On開頭, 接受EventArgs類型的參數:

        public void OnFallsIll()
        {
            FallsIll?.Invoke(this, new FallsIllEventArgs("China Beijing"));
        }

 

 

注意: 預定義的非泛型的EventHandler委托可以在沒有數據需要傳輸的時候使用, 調用的時候可以使用EventArgs.Empty來避免不必要的初始化EventArgs.

 

用.net core 實現觀察者模式的代碼:

Person.cs

using System;

namespace ObserverPattern
{
    public class Person
    {
        public event EventHandler<FallsIllEventArgs> FallsIll;

        public void OnFallsIll()
        {
            FallsIll?.Invoke(this, new FallsIllEventArgs("China Beijing"));
        }

    }
}

 

 
FallsIllEventArgs.cs:
using System;

namespace ObserverPattern
{
    public class FallsIllEventArgs : EventArgs
    {
        public readonly string Address;

        public FallsIllEventArgs(string address)
        {
            this.Address = address;
        }
    }
}

 

Program.cs:
using System;

namespace ObserverPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            var person = new Person();
            person.FallsIll += OnFallsIll;
            person.OnFallsIll();
            person.FallsIll -= OnFallsIll;
        }

        private static void OnFallsIll(object sender, FallsIllEventArgs eventArgs)
        {
            Console.WriteLine($"A doctor has been called to {eventArgs.Address}");
        }
    }
}

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM