paint-brush
.NET C# 中观察者设计模式的基本指南经过@ahmedtarekhasan
3,280 讀數
3,280 讀數

.NET C# 中观察者设计模式的基本指南

经过 Ahmed Tarek Hasan19m2023/04/10
Read on Terminal Reader

太長; 讀書

在本文中,您将了解 .NET C# 中的观察者设计模式以及一些增强功能。观察者设计模式使订阅者能够向提供者注册并接收来自提供者的通知。适用于任何需要推送通知的场景。该模式的独特之处在于,使用它您可以在没有紧密耦合关系的情况下实现这一点。
featured image - .NET C# 中观察者设计模式的基本指南
Ahmed Tarek Hasan HackerNoon profile picture
0-item

在本文中,您将了解 .NET C# 中的观察者设计模式以及一些增强功能。


观察者设计模式定义

观察者设计模式是最重要和最常用的设计模式之一。


首先,让我们检查一下观察者设计模式的正式定义。


按照微软的文档:


观察者设计模式使订阅者能够向提供者注册并接收来自提供者的通知。适用于任何需要推送通知的场景。该模式定义了一个提供者(也称为主题或可观察对象)和零个、一个或多个观察者。观察者向提供者注册,每当发生预定义的条件、事件或状态更改时,提供者都会通过调用观察者的方法之一自动通知所有观察者。在此方法调用中,提供者还可以向观察者提供当前状态信息。在 .NET 中,通过实现通用System.IObservable<T>System.IObserver<T>接口来应用观察者设计模式。通用类型参数表示提供通知信息的类型。


因此,从上面的定义,我们可以理解以下内容:

  1. 我们有两个派对或模块。
  2. 提供一些信息流的模块。这个模块被称为Provider (因为它提供信息),或Subject (因为它将信息提供给外界),或Observable (因为它可以被外界观察到)。
  3. 对来自其他地方的信息流感兴趣的模块。这个模块被称为观察者(因为它观察信息)。

Den Harrson 在 Unsplash 上拍摄的照片

观察者设计模式的优点

正如我们现在所知, Observer 设计模式制定了ObservableObserver模块之间的关系。观察者设计模式的独特之处在于,使用它您可以在没有紧密耦合关系的情况下实现这一点。


分析模式的工作方式,您会发现以下内容:

  1. Observable知道关于Observer所需的最少信息。
  2. Observer知道关于Observable所需的最少信息。
  3. 甚至相互知识也是通过抽象实现的,而不是具体的实现。
  4. 最后,两个模块都可以完成他们的工作,而且只能完成他们的工作。

Lucas Santos 在 Unsplash 上拍摄的照片

使用的抽象

这些是用于在.NET C#中实现观察者设计模式的抽象



IObservable<输出T>

这是代表任何Observable 的Covariant接口。如果你想了解更多关于 .NET 中的 Variance,你可以查看这篇文章.NET C# 中的协变和逆变.


该接口中定义的成员有:


 public IDisposable Subscribe (IObserver<out T> observer);


应该调用Subscribe方法来通知Observable某个Observer对其信息流感兴趣。


Subscribe方法返回一个实现IDisposable接口的对象。然后Observer可以使用该对象取消订阅Observable提供的信息流。完成此操作后,将不会通知观察者有关信息流的任何更新。



IObserver<in T>

这是一个代表任何Observer 的逆变接口。如果你想了解更多关于 .NET 中的 Variance,你可以查看这篇文章.NET C# 中的协变和逆变.


该接口中定义的成员有:


 public void OnCompleted (); public void OnError (Exception error); public void OnNext (T value);


Observable应该调用OnCompleted方法来通知Observer信息流已经完成并且Observer不应该期待任何更多信息。


Observable应该调用OnError方法来通知Observer发生了错误。


Observable应该调用OnNext方法来通知Observer一条新的信息已经准备好并且正在添加到流中。


Tadas Sar 在 Unsplash 上拍摄的照片

微软的实施

现在,让我们看看Microsoft如何推荐在 C# 中实现观察者设计模式。稍后,我将向您展示我自己实现的一些小改进。


我们将构建一个简单的天气预报控制台应用程序。在这个应用程序中,我们将拥有WeatherForecast模块(Observable、Provider、Subject)和WeatherForecastObserver模块(Observer)。


那么,让我们开始研究实现。



天气资讯

namespace Observable { public class WeatherInfo { internal WeatherInfo(double temperature) { Temperature = temperature; } public double Temperature { get; } } }


这是表示要在信息流中流动的信息片段的实体。



天气预报

using System; using System.Collections.Generic; namespace Observable { public class WeatherForecast : IObservable<WeatherInfo> { private readonly List<IObserver<WeatherInfo>> m_Observers; private readonly List<WeatherInfo> m_WeatherInfoList; public WeatherForecast() { m_Observers = new List<IObserver<WeatherInfo>>(); m_WeatherInfoList = new List<WeatherInfo>(); } public IDisposable Subscribe(IObserver<WeatherInfo> observer) { if (!m_Observers.Contains(observer)) { m_Observers.Add(observer); foreach (var item in m_WeatherInfoList) { observer.OnNext(item); } } return new WeatherForecastUnsubscriber(m_Observers, observer); } public void RegisterWeatherInfo(WeatherInfo weatherInfo) { m_WeatherInfoList.Add(weatherInfo); foreach (var observer in m_Observers) { observer.OnNext(weatherInfo); } } public void ClearWeatherInfo() { m_WeatherInfoList.Clear(); } } }


我们可以在这里注意到:

  1. WeatherForecast类正在实现IObservable<WeatherInfo>
  2. Subscribe方法的实现中,我们检查传入的 Observer 之前是否已经注册过。如果没有,我们将其添加到本地m_Observers观察者列表中。然后,我们逐一循环本地m_WeatherInfoList列表中的所有WeatherInfo条目,并通过调用 Observer 的OnNext方法通知 Observer。
  3. 最后,我们返回一个WeatherForecastUnsubscriber类的新实例,供 Observer 用于取消订阅信息流。
  4. 定义了RegisterWeatherInfo方法,以便主模块可以注册新的WeatherInfo 。在现实世界中,这可以替换为内部计划的API 调用SignalR Hub的侦听器或其他可以充当信息源的东西。



退订者<T>

 using System; using System.Collections.Generic; namespace Observable { public class Unsubscriber<T> : IDisposable { private readonly List<IObserver<T>> m_Observers; private readonly IObserver<T> m_Observer; private bool m_IsDisposed; public Unsubscriber(List<IObserver<T>> observers, IObserver<T> observer) { m_Observers = observers; m_Observer = observer; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (m_IsDisposed) return; if (disposing && m_Observers.Contains(m_Observer)) { m_Observers.Remove(m_Observer); } m_IsDisposed = true; } ~Unsubscriber() { Dispose(false); } } }


我们可以在这里注意到:

  1. 这是任何取消订阅者的基类。
  2. 它通过应用一次性设计模式来实现IDisposable
  3. 通过构造函数,它接收完整的 Observer 列表以及为其创建的 Observer。
  4. 在处理时,它会检查观察者是否已经存在于观察者的完整列表中。如果是,则将其从列表中删除。



天气预报退订

using System; using System.Collections.Generic; namespace Observable { public class WeatherForecastUnsubscriber : Unsubscriber<WeatherInfo> { public WeatherForecastUnsubscriber( List<IObserver<WeatherInfo>> observers, IObserver<WeatherInfo> observer) : base(observers, observer) { } } }


我们可以在这里注意到:

  1. 这是从Unsubscriber<T>类继承的。
  2. 没有特殊处理发生。



天气预报观察员

using System; namespace Observable { public class WeatherForecastObserver : IObserver<WeatherInfo> { private IDisposable m_Unsubscriber; public virtual void Subscribe(WeatherForecast provider) { m_Unsubscriber = provider.Subscribe(this); } public virtual void Unsubscribe() { m_Unsubscriber.Dispose(); } public void OnCompleted() { Console.WriteLine("Completed"); } public void OnError(Exception error) { Console.WriteLine("Error"); } public void OnNext(WeatherInfo value) { Console.WriteLine($"Temperature: {value.Temperature}"); } } }


我们可以在这里注意到:

  1. WeatherForecastObserver类正在实现IObserver<WeatherInfo>
  2. OnNext方法中,我们将温度写入控制台。
  3. OnCompleted方法中,我们将“Completed”写入控制台。
  4. OnError方法中,我们将“Error”写入控制台。
  5. 我们定义了void Subscribe(WeatherForecast provider)方法以允许主模块触发注册过程。返回的取消订阅者对象在内部保存,以备取消订阅时使用。
  6. 使用相同的概念,定义了void Unsubscribe()方法,它使用内部保存的取消订阅者对象。



程序

using System; namespace Observable { class Program { static void Main(string[] args) { var provider = new WeatherForecast(); provider.RegisterWeatherInfo(new WeatherInfo(1)); provider.RegisterWeatherInfo(new WeatherInfo(2)); provider.RegisterWeatherInfo(new WeatherInfo(3)); var observer = new WeatherForecastObserver(); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(4)); provider.RegisterWeatherInfo(new WeatherInfo(5)); observer.Unsubscribe(); provider.RegisterWeatherInfo(new WeatherInfo(6)); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(7)); Console.ReadLine(); } } }


我们可以在这里注意到:

  1. 我们创建了提供者的一个实例。
  2. 然后注册了3条信息。
  3. 到目前为止,由于没有定义任何观察者,因此不应将任何内容记录到控制台。
  4. 然后创建了一个观察者的实例。
  5. 然后订阅观察者流。
  6. 此时,我们应该在控制台中找到 3 个记录的温度。这是因为当观察者订阅时,它会收到有关已经存在的信息的通知,在我们的例子中,它们是 3 条信息。
  7. 然后我们注册2条信息。
  8. 因此,我们在控制台中记录了另外 2 条消息。
  9. 然后我们退订。
  10. 然后我们注册 1 条信息。
  11. 但是,这条信息不会记录到控制台,因为观察者已经取消订阅。
  12. 然后观察者再次订阅。
  13. 然后我们注册 1 条信息。
  14. 因此,这条信息被记录到控制台。


最后,运行它应该以这个结果结束:


图片由 Ahmed Tarek 提供


Bruno Yamazaky 在 Unsplash 上拍摄的照片

我的扩展实现

当我检查微软的实现时,我发现了一些问题。因此,我决定做一些小改动。


IExtendedObservable<输出 T>

 using System; using System.Collections.Generic; namespace ExtendedObservable { public interface IExtendedObservable<out T> : IObservable<T> { IReadOnlyCollection<T> Snapshot { get; } IDisposable Subscribe(IObserver<T> observer, bool withHistory); } }


我们可以在这里注意到:

  1. IExtendedObservable<out T>接口扩展了IObservable<T>接口。
  2. 它是协变的。如果您想了解更多相关信息,可以查看文章.NET C# 中的协变和逆变.
  3. 我们定义了IReadOnlyCollection<T> Snapshot属性,以允许其他模块无需订阅即可获取现有信息条目的即时列表。
  4. 我们还使用额外的bool withHistory参数定义了IDisposable Subscribe(IObserver<T> observer, bool withHistory)方法,以便 Observer 可以决定是否要在订阅时收到有关已存在信息条目的通知。



退订

using System; namespace ExtendedObservable { public class Unsubscriber : IDisposable { private readonly Action m_UnsubscribeAction; private bool m_IsDisposed; public Unsubscriber(Action unsubscribeAction) { m_UnsubscribeAction = unsubscribeAction; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (m_IsDisposed) return; if (disposing) { m_UnsubscribeAction(); } m_IsDisposed = true; } ~Unsubscriber() { Dispose(false); } } }


我们可以在这里注意到:

  1. 现在, Unsubscriber类不是通用的。
  2. 这是因为它不再需要知道信息实体的类型。
  3. 它没有访问完整的观察者列表和它为其创建的观察者,而是在它被处置时通知 Observable 并且 Observable 自己处理注销过程。
  4. 这样,它做的比以前少,它只是在做它的工作。



天气预报退订

using System; using System.Collections.Generic; namespace ExtendedObservable { public class WeatherForecastUnsubscriber : Unsubscriber { public WeatherForecastUnsubscriber( Action unsubscribeAction) : base(unsubscribeAction) { } } }


我们可以在这里注意到:

  1. 我们从Unsubscriber<T>中删除了<T>部分。
  2. 现在构造函数接收一个Action以在处理时调用。



天气预报

using System; using System.Collections.Generic; namespace ExtendedObservable { public class WeatherForecast : IExtendedObservable<WeatherInfo> { private readonly List<IObserver<WeatherInfo>> m_Observers; private readonly List<WeatherInfo> m_WeatherInfoList; public WeatherForecast() { m_Observers = new List<IObserver<WeatherInfo>>(); m_WeatherInfoList = new List<WeatherInfo>(); } public IReadOnlyCollection<WeatherInfo> Snapshot => m_WeatherInfoList; public IDisposable Subscribe(IObserver<WeatherInfo> observer) { return Subscribe(observer, false); } public IDisposable Subscribe(IObserver<WeatherInfo> observer, bool withHistory) { if (!m_Observers.Contains(observer)) { m_Observers.Add(observer); if (withHistory) { foreach (var item in m_WeatherInfoList) { observer.OnNext(item); } } } return new WeatherForecastUnsubscriber( () => { if (m_Observers.Contains(observer)) { m_Observers.Remove(observer); } }); } public void RegisterWeatherInfo(WeatherInfo weatherInfo) { m_WeatherInfoList.Add(weatherInfo); foreach (var observer in m_Observers) { observer.OnNext(weatherInfo); } } public void ClearWeatherInfo() { m_WeatherInfoList.Clear(); } } }


我们可以在这里注意到:

  1. 它几乎相同,除了IReadOnlyCollection<WeatherInfo> Snapshot属性返回内部m_WeatherInfoList列表但作为IReadOnlyCollection
  2. IDisposable Subscribe(IObserver<WeatherInfo> observer, bool withHistory)方法使用了withHistory参数。



天气预报观察员

using System; namespace ExtendedObservable { public class WeatherForecastObserver : IObserver<WeatherInfo> { private IDisposable m_Unsubscriber; public virtual void Subscribe(WeatherForecast provider) { m_Unsubscriber = provider.Subscribe(this, true); } public virtual void Unsubscribe() { m_Unsubscriber.Dispose(); } public void OnCompleted() { Console.WriteLine("Completed"); } public void OnError(Exception error) { Console.WriteLine("Error"); } public void OnNext(WeatherInfo value) { Console.WriteLine($"Temperature: {value.Temperature}"); } } }


我们在这里可以注意到,它几乎是相同的,除了Subscribe(WeatherForecast provider)现在决定是否应该Subscribe历史。



程序

using System; namespace ExtendedObservable { class Program { static void Main(string[] args) { var provider = new WeatherForecast(); provider.RegisterWeatherInfo(new WeatherInfo(1)); provider.RegisterWeatherInfo(new WeatherInfo(2)); provider.RegisterWeatherInfo(new WeatherInfo(3)); var observer = new WeatherForecastObserver(); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(4)); provider.RegisterWeatherInfo(new WeatherInfo(5)); observer.Unsubscribe(); provider.RegisterWeatherInfo(new WeatherInfo(6)); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(7)); Console.ReadLine(); } } }


和以前一样。




最后,运行它应该会得到与之前相同的结果:


图片由 Ahmed Tarek 提供


照片由 Emily Morter 在 Unsplash 上拍摄,由 Ahmed Tarek 调整

下一步是什么

现在,您了解了 .NET C# 中观察者设计模式的基础知识。然而,这并不是故事的结局。


IObservable<T>IObserver<T>接口之上构建的库提供了更多您可能会觉得有用的很酷的特性和功能。


这些库中的一个是.NET (Rx) 的响应式扩展图书馆。它由一组扩展方法和 LINQ 标准序列运算符组成,以支持异步编程。


因此,我鼓励您探索这些库并尝试一下。我相信你会喜欢其中的一些。


也发布在这里。