paint-brush
Hướng dẫn cơ bản về mẫu thiết kế quan sát viên trong .NET C#từ tác giả@ahmedtarekhasan
3,303 lượt đọc
3,303 lượt đọc

Hướng dẫn cơ bản về mẫu thiết kế quan sát viên trong .NET C#

từ tác giả Ahmed Tarek Hasan19m2023/04/10
Read on Terminal Reader

dài quá đọc không nổi

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 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 trường hợp yêu cầu thông báo dựa trên đẩy. Điều làm cho mẫu 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ẽ.
featured image - Hướng dẫn cơ bản về mẫu thiết kế quan sát viên trong .NET C#
Ahmed Tarek Hasan HackerNoon profile picture
0-item

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 tài liệu của Microsoft :


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>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:

  1. Chúng tôi có hai bên hoặc mô-đun.
  2. 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).
  3. 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).

Ảnh của Den Harrson trên Bapt

Ư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átNgườ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:

  1. Observable biết thông tin tối thiểu cần thiết về Observer .
  2. Người quan sát biết thông tin tối thiểu cần thiết về Người quan sát .
  3. 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ể.
  4. 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.

Ảnh của Lucas Santos trên Bapt

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 Hiệp phương sai và Chống phương sai trong .NET C# .


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 Hiệp phương sai và Chống phương sai trong .NET C# .


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.


Ảnh của Tadas Sar trên Bapt

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:

  1. Lớp WeatherForecast đang triển khai IObservable<WeatherInfo> .
  2. 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á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.
  3. 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.
  4. 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:

  1. Đây là lớp cơ sở cho bất kỳ Người không đăng ký nào.
  2. Nó triển khai IDisposable bằng cách áp dụng Disposable Design Pattern .
  3. 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.
  4. 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:

  1. Điều này kế thừa từ lớp Unsubscriber<T> .
  2. 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:

  1. Lớp WeatherForecastObserver đang triển khai IObserver<WeatherInfo> .
  2. Trên phương thức OnNext , chúng tôi đang ghi nhiệt độ vào bàn điều khiển.
  3. Trên phương thức OnCompleted , chúng ta đang viết “Completed” vào bảng điều khiển.
  4. Trên phương thức OnError , chúng tôi đang ghi “Lỗi” vào bảng điều khiển.
  5. 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ý.
  6. 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:

  1. Chúng tôi đã tạo một phiên bản của nhà cung cấp.
  2. Sau đó đăng ký 3 mẩu thông tin.
  3. 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.
  4. Sau đó, tạo một thể hiện của người quan sát.
  5. Sau đó đăng ký người quan sát vào luồng.
  6. 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.
  7. Sau đó, chúng tôi đăng ký 2 mẩu thông tin.
  8. 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.
  9. Sau đó, chúng tôi hủy đăng ký.
  10. Sau đó, chúng tôi đăng ký 1 mẩu thông tin.
  11. 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ý.
  12. Sau đó, người quan sát đăng ký lại.
  13. Sau đó, chúng tôi đăng ký 1 mẩu thông tin.
  14. 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:


Hình ảnh của Ahmed Tarek


Ảnh của Bruno Yamazaky trên Bapt

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:

  1. Giao diện IExtendedObservable<out T> mở rộng giao diện IObservable<T> .
  2. 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# .
  3. 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ý.
  4. 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:

  1. Bây giờ, lớp Unsubscriber không phải là chung chung.
  2. Điều này là do không cần biết thêm về loại thực thể thông tin.
  3. 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ý.
  4. 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:

  1. Chúng tôi đã xóa phần <T> khỏi Unsubscriber<T> .
  2. 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:

  1. Nó gần như giống nhau ngoại trừ thuộc tính IReadOnlyCollection<WeatherInfo> Snapshot trả về danh sách m_WeatherInfoList nội bộ nhưng dưới dạng IReadOnlyCollection .
  2. 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:


Hình ảnh của Ahmed Tarek


Ảnh của Emily Morter trên Unsplash, được điều chỉnh bởi Ahmed Tarek

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>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à Tiện ích mở rộng phản ứng cho .NET (Rx) thư viện. Nó bao gồm một tập hợp các phương thức mở rộng và các toán tử tuần tự chuẩn LINQ để hỗ trợ lập trình không đồng bộ.


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.