Trong bài viết này, bạn sẽ tìm hiểu về Mẫu thiết kế Người quan sát trong .NET C# với một số cải tiến.
Mẫu thiết kế Người quan sát là một trong những mẫu thiết kế quan trọng nhất và thường được sử dụng.
Trước tiên, hãy kiểm tra định nghĩa chính thức của Mẫu thiết kế Người quan sát .
Theo
Mẫu thiết kế người quan sát cho phép người đăng ký đăng ký và nhận thông báo từ nhà cung cấp. Nó phù hợp với mọi tình huống yêu cầu thông báo dựa trên đẩy. Mẫu xác định một nhà cung cấp (còn được gọi là chủ thể hoặc có thể quan sát được) và không, một hoặc nhiều người quan sát. Người quan sát đăng ký với nhà cung cấp và bất cứ khi nào xảy ra điều kiện, sự kiện hoặc thay đổi trạng thái được xác định trước, nhà cung cấp sẽ tự động thông báo cho tất cả người quan sát bằng cách gọi một trong các phương thức của họ. Trong cuộc gọi phương thức này, nhà cung cấp cũng có thể cung cấp thông tin trạng thái hiện tại cho người quan sát. Trong .NET, mẫu thiết kế trình quan sát được áp dụng bằng cách triển khai các giao diện System.IObservable<T> và System.IObserver<T> chung. Tham số loại chung đại diện cho loại cung cấp thông tin thông báo.
Như chúng ta đã biết, Mẫu thiết kế Người quan sát hình thành mối quan hệ giữa các mô-đun Có thể quan sát và Người quan sát . Điều làm cho Mẫu thiết kế Người quan sát trở nên độc đáo là khi sử dụng nó, bạn có thể đạt được điều này mà không cần có mối quan hệ liên kết chặt chẽ.
Phân tích cách thức hoạt động của mô hình, bạn sẽ tìm thấy những điều sau:
Đây là những phần trừu tượng được sử dụng để triển khai Mẫu thiết kế Người quan sát trong .NET C# .
Đây là một giao diện Covariant đại diện cho bất kỳ Observable nào. Nếu bạn muốn biết thêm về Variance trong .NET, bạn có thể xem bài viết
Các thành viên được định nghĩa trong giao diện này là:
public IDisposable Subscribe (IObserver<out T> observer);
Phương thức Subscribe
nên được gọi để thông báo cho Observable rằng một số Observer quan tâm đến luồng thông tin của nó.
Phương thức Subscribe
trả về một đối tượng cài đặt giao diện IDisposable
. Sau đó, đối tượng này có thể được Người quan sát sử dụng để hủy đăng ký khỏi luồng thông tin do Người quan sát cung cấp. Khi điều này được thực hiện, Người quan sát sẽ không được thông báo về bất kỳ cập nhật nào đối với luồng thông tin.
Đây là giao diện Contravariant đại diện cho bất kỳ Người quan sát nào . Nếu bạn muốn biết thêm về Variance trong .NET, bạn có thể xem bài viết
Các thành viên được định nghĩa trong giao diện này là:
public void OnCompleted (); public void OnError (Exception error); public void OnNext (T value);
Phương thức OnCompleted
nên được gọi bởi Observable để thông báo cho Người quan sát rằng luồng thông tin đã hoàn thành và Người quan sát không nên mong đợi thêm bất kỳ thông tin nào.
Phương thức OnError
nên được gọi bởi Observable để thông báo cho Người quan sát rằng đã xảy ra lỗi.
Phương thức OnNext
nên được gọi bởi Observable để thông báo cho Người quan sát rằng một phần thông tin mới đã sẵn sàng và đang được thêm vào luồng.
Bây giờ, hãy xem cách Microsoft khuyến nghị triển khai Mẫu thiết kế Người quan sát trong C#. Sau đó, tôi sẽ cho bạn thấy một số cải tiến nhỏ do chính tôi thực hiện.
Chúng tôi sẽ xây dựng một Ứng dụng bảng điều khiển dự báo thời tiết đơn giản. Trong ứng dụng này, chúng ta sẽ có mô-đun WeatherForecast (Có thể quan sát, Nhà cung cấp, Chủ đề) và mô-đun WeatherForecastObserver (Người quan sát).
Vì vậy, hãy bắt đầu xem xét việc thực hiện.
namespace Observable { public class WeatherInfo { internal WeatherInfo(double temperature) { Temperature = temperature; } public double Temperature { get; } } }
Đây là thực thể đại diện cho phần thông tin được truyền trong luồng thông tin.
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(); } } }
Những gì chúng ta có thể nhận thấy ở đây:
WeatherForecast
đang triển khai IObservable<WeatherInfo>
.Subscribe
, chúng tôi kiểm tra xem thông qua trong Người quan sát đã được đăng ký trước đó hay chưa. Nếu không, chúng tôi thêm nó vào danh sách người quan sát m_Observers
cục bộ. Sau đó, chúng tôi lặp lại tất cả các mục WeatherInfo
mà chúng tôi có trong danh sách m_WeatherInfoList
cục bộ từng cái một và thông báo cho Người quan sát về điều đó bằng cách gọi phương thức OnNext
của Người quan sát.WeatherForecastUnsubscriber
sẽ được Người quan sát sử dụng để hủy đăng ký luồng thông tin.RegisterWeatherInfo
được xác định để mô-đun chính có thể đăng ký WeatherInfo
mới. Trong thế giới thực, điều này có thể được thay thế bằng lệnh gọi API được lên lịch nội bộ hoặc trình nghe đối với Trung tâm SignalR hoặc thứ gì đó khác sẽ hoạt động như một nguồn thông tin.
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); } } }
Những gì chúng ta có thể nhận thấy ở đây:
IDisposable
bằng cách áp dụng Disposable Design Pattern .
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) { } } }
Những gì chúng ta có thể nhận thấy ở đây:
Unsubscriber<T>
.
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}"); } } }
Những gì chúng ta có thể nhận thấy ở đây:
WeatherForecastObserver
đang triển khai IObserver<WeatherInfo>
.OnNext
, chúng tôi đang ghi nhiệt độ vào bàn điều khiển.OnCompleted
, chúng ta đang viết “Completed” vào bảng điều khiển.OnError
, chúng tôi đang ghi “Lỗi” vào bảng điều khiển.void Subscribe(WeatherForecast provider)
để cho phép mô-đun chính kích hoạt quá trình đăng ký. Đối tượng hủy đăng ký được trả về được lưu nội bộ để sử dụng trong trường hợp hủy đăng ký.void Unsubscribe()
được định nghĩa và nó sử dụng đối tượng un-subscriber được lưu nội bộ.
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(); } } }
Những gì chúng ta có thể nhận thấy ở đây:
Khi kiểm tra quá trình triển khai của Microsoft, tôi thấy có một số lo ngại. Do đó, tôi quyết định thực hiện một số thay đổi nhỏ.
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); } }
Những gì chúng ta có thể nhận thấy ở đây:
IExtendedObservable<out T>
mở rộng giao diện IObservable<T>
.IReadOnlyCollection<T> Snapshot
để cho phép các mô-đun khác nhận danh sách tức thời các mục nhập thông tin đã tồn tại mà không cần phải đăng ký.IDisposable Subscribe(IObserver<T> observer, bool withHistory)
với tham số bool withHistory
bổ sung để Người quan sát có thể quyết định xem nó có muốn nhận thông báo về các mục nhập thông tin hiện có hay không tại thời điểm đăng ký.
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); } } }
Những gì chúng ta có thể nhận thấy ở đây:
Unsubscriber
không phải là chung chung.
using System; using System.Collections.Generic; namespace ExtendedObservable { public class WeatherForecastUnsubscriber : Unsubscriber { public WeatherForecastUnsubscriber( Action unsubscribeAction) : base(unsubscribeAction) { } } }
Những gì chúng ta có thể nhận thấy ở đây:
<T>
khỏi Unsubscriber<T>
.Action
sẽ được gọi trong trường hợp xử lý.
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(); } } }
Những gì chúng ta có thể nhận thấy ở đây:
IReadOnlyCollection<WeatherInfo> Snapshot
trả về danh sách m_WeatherInfoList
nội bộ nhưng dưới dạng IReadOnlyCollection
.IDisposable Subscribe(IObserver<WeatherInfo> observer, bool withHistory)
sử dụng tham số 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}"); } } }
Những gì chúng ta có thể nhận thấy ở đây là nó gần như giống nhau ngoại trừ Subscribe(WeatherForecast provider)
hiện quyết định xem có nên Subscribe
với lịch sử hay không.
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(); } } }
Nó cũng giống như trước đây.
Bây giờ, bạn đã biết những kiến thức cơ bản về Observer Design Pattern trong .NET C#. Tuy nhiên, đây không phải là kết thúc của câu chuyện.
Có các thư viện được xây dựng trên các giao diện IObservable<T>
và IObserver<T>
cung cấp nhiều tính năng và khả năng thú vị hơn mà bạn có thể thấy hữu ích.
Trên các thư viện này là
Do đó, tôi khuyến khích bạn khám phá các thư viện này và dùng thử. Tôi chắc chắn rằng bạn muốn một số trong số họ.
Cũng được xuất bản ở đây.