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.
Định nghĩa mẫu thiết kế của người quan sát
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ư vậy, từ định nghĩa trên, chúng ta có thể hiểu như sau:
- Chúng tôi có hai bên hoặc mô-đun.
- Mô-đun có một số luồng thông tin để cung cấp. Mô-đun này được gọi là Nhà cung cấp (vì nó cung cấp thông tin) hoặc Chủ thể (vì nó đưa thông tin ra thế giới bên ngoài) hoặc Có thể quan sát được (vì nó có thể được quan sát bởi thế giới bên ngoài).
- Mô-đun quan tâm đến luồng thông tin đến từ một nơi khác. Mô-đun này được gọi là Người quan sát (vì nó quan sát thông tin).
Ưu điểm của mẫu thiết kế quan sát
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:
- Observable biết thông tin tối thiểu cần thiết về Observer .
- Người quan sát biết thông tin tối thiểu cần thiết về Người quan sát .
- Ngay cả kiến thức lẫn nhau cũng đạt được thông qua trừu tượng hóa, không phải triển khai cụ thể.
- Cuối cùng, cả hai mô-đun đều có thể thực hiện công việc của chúng và chỉ công việc của chúng.
Trừu tượng được sử dụng
Đâ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# .
IObservable<ra T>
Đâ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.
IObserver<trong T>
Đâ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.
Triển khai của Microsoft
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.
Thông tin thời tiết
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.
Dự báo thời tiết
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:
- Lớp
WeatherForecast
đang triển khaiIObservable<WeatherInfo>
. - Khi triển khai phương thức
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átm_Observers
cục bộ. Sau đó, chúng tôi lặp lại tất cả các mụcWeatherInfo
mà chúng tôi có trong danh sáchm_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ứcOnNext
của Người quan sát. - Cuối cùng, chúng tôi trả về một phiên bản mới của lớp
WeatherForecastUnsubscriber
sẽ được Người quan sát sử dụng để hủy đăng ký luồng thông tin. - Phương thức
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.
Người hủy đăng ký<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); } } }
Những gì chúng ta có thể nhận thấy ở đây:
- Đây là lớp cơ sở cho bất kỳ Người không đăng ký nào.
- Nó triển khai
IDisposable
bằng cách áp dụng Disposable Design Pattern . - Thông qua hàm tạo, nó nhận danh sách đầy đủ của Người quan sát và Người quan sát mà nó được tạo.
- Trong khi xử lý, nó sẽ kiểm tra xem Người quan sát đã tồn tại trong danh sách Người quan sát đầy đủ chưa. Nếu có, nó sẽ xóa nó khỏi danh sách.
WeatherForecastHủy đăng ký
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:
- Điều này kế thừa từ lớp
Unsubscriber<T>
. - Không có xử lý đặc biệt đang xảy ra.
WeatherForecastNgười quan sá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:
- Lớp
WeatherForecastObserver
đang triển khaiIObserver<WeatherInfo>
. - Trên phương thức
OnNext
, chúng tôi đang ghi nhiệt độ vào bàn điều khiển. - Trên phương thức
OnCompleted
, chúng ta đang viết “Completed” vào bảng điều khiển. - Trên phương thức
OnError
, chúng tôi đang ghi “Lỗi” vào bảng điều khiển. - Chúng tôi đã xác định phương thức
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ý. - Sử dụng cùng một khái niệm, phương thức
void Unsubscribe()
được định nghĩa và nó sử dụng đối tượng un-subscriber được lưu nội bộ.
Chương trình
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:
- Chúng tôi đã tạo một phiên bản của nhà cung cấp.
- Sau đó đăng ký 3 mẩu thông tin.
- Cho đến thời điểm này, không có gì được ghi vào bảng điều khiển vì không có người quan sát nào được xác định.
- Sau đó, tạo một thể hiện của người quan sát.
- Sau đó đăng ký người quan sát vào luồng.
- Tại thời điểm này, chúng ta sẽ tìm thấy 3 nhiệt độ đã ghi trong bảng điều khiển. Điều này là do khi người quan sát đăng ký, nó sẽ được thông báo về thông tin đã có và trong trường hợp của chúng tôi, chúng là 3 mẩu thông tin.
- Sau đó, chúng tôi đăng ký 2 mẩu thông tin.
- Vì vậy, chúng tôi nhận được thêm 2 tin nhắn được ghi vào bảng điều khiển.
- Sau đó, chúng tôi hủy đăng ký.
- Sau đó, chúng tôi đăng ký 1 mẩu thông tin.
- Tuy nhiên, phần thông tin này sẽ không được ghi vào bảng điều khiển vì người quan sát đã hủy đăng ký.
- Sau đó, người quan sát đăng ký lại.
- Sau đó, chúng tôi đăng ký 1 mẩu thông tin.
- Vì vậy, phần thông tin này được ghi vào bảng điều khiển.
Cuối cùng, chạy cái này sẽ có kết quả như sau:
Triển khai mở rộng của tôi
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ỏ.
IExtendedObservable<ra 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); } }
Những gì chúng ta có thể nhận thấy ở đây:
- Giao diện
IExtendedObservable<out T>
mở rộng giao diệnIObservable<T>
. - Nó là Hiệp biến . Nếu bạn muốn biết thêm về điều này, bạn có thể kiểm tra bài viết
Hiệp phương sai và Chống phương sai trong .NET C# . - Chúng tôi đã xác định thuộc tính
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ý. - Chúng tôi cũng đã xác định phương thức
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ý.
hủy đă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:
- Bây giờ, lớp
Unsubscriber
không phải là chung chung. - Điều này là do không cần biết thêm về loại thực thể thông tin.
- Thay vì có quyền truy cập vào danh sách đầy đủ của Người quan sát và Người quan sát mà nó được tạo, nó chỉ thông báo cho Người quan sát khi nó được xử lý và Người quan sát có thể tự xử lý quá trình hủy đăng ký.
- Bằng cách này, nó đang làm ít hơn trước và nó chỉ đang làm công việc của mình.
WeatherForecastHủy đăng ký
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:
- Chúng tôi đã xóa phần
<T>
khỏiUnsubscriber<T>
. - Và bây giờ, hàm tạo thực hiện một
Action
sẽ được gọi trong trường hợp xử lý.
Dự báo thời tiết
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:
- Nó gần như giống nhau ngoại trừ thuộc tính
IReadOnlyCollection<WeatherInfo> Snapshot
trả về danh sáchm_WeatherInfoList
nội bộ nhưng dưới dạngIReadOnlyCollection
. - Và phương thức
IDisposable Subscribe(IObserver<WeatherInfo> observer, bool withHistory)
sử dụng tham sốwithHistory
.
WeatherForecastNgười quan sát
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.
Chương trình
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.
Cuối cùng, chạy cái này sẽ có kết quả giống như trước đây:
Cái gì tiếp theo
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.