ШАБЛОНЫ ПРОЕКТИРОВАНИЯ

Шаблон проектирования Observer в .NET C#

Узнайте о шаблоне проектирования Observer в .NET C# с некоторыми улучшениями.

Шаблон проектирования ObserverОпределение

Шаблон проектирования Observer является одним из наиболее важных и часто используемых шаблонов проектирования, и это не случайно.

Во-первых, давайте проверим формальное определение шаблона проектирования Observer.

Согласно Документации Microsoft:

Шаблон проектирования наблюдателя позволяет подписчику регистрироваться и получать уведомления от поставщика. Он подходит для любого сценария, требующего push-уведомлений. Шаблон определяет провайдера (также известного как субъект или наблюдаемый объект) и ноль, одного или нескольких наблюдателей. Наблюдатели регистрируются у провайдера, и всякий раз, когда происходит предопределенное условие, событие или изменение состояния, провайдер автоматически уведомляет всех наблюдателей, вызывая один из их методов. В этом вызове метода провайдер также может предоставить наблюдателям информацию о текущем состоянии. В .NET шаблон проектирования наблюдателя применяется путем реализации общих интерфейсов System.IObservable‹T› и System.IObserver‹T›. Параметр универсального типа представляет тип, предоставляющий сведения об уведомлении.

Итак, из приведенного выше определения мы можем понять следующее:

  1. У нас есть две стороны или модули.
  2. Модуль, который имеет некоторый поток информации для предоставления. Этот модуль называется Provider (поскольку он предоставляет информацию), или Subject (поскольку он передает информацию во внешний мир), или Observable (поскольку он могут быть замечены внешним миром).
  3. Модуль, которому интересен поток информации, приходящий откуда-то еще. Этот модуль называется Observer (поскольку он наблюдает за информацией).



Преимущества шаблона проектирования Observer

Как мы теперь знаем, Шаблон проектирования Observer формулирует связь между модулями Observable и Observer. Что делает Шаблон проектирования Observer уникальным, так это то, что с его помощью вы можете достичь этого без жесткой связи.

Анализируя работу шаблона, вы обнаружите следующее:

  1. Observable знает минимальную информацию, необходимую о Observer.
  2. Наблюдатель знает минимальную информацию, необходимую о Наблюдаемом.
  3. Даже взаимное знание достигается за счет абстракций, а не конкретных реализаций.
  4. В конце концов, оба модуля могут выполнять свою работу и только свою работу.

Используемые абстракции

Это абстракции, используемые для реализации шаблона проектирования Observer в .NET C#.

IObservable‹вне T›

Это Covariant интерфейс, представляющий любой Observable. Если вы хотите узнать больше о дисперсии в .NET, вы можете прочитать статью Ковариация и контравариантность в .NET C#.

Члены, определенные в этом интерфейсе:

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

Следует вызвать метод Subscribe, чтобы сообщить Observable, что какой-то Observer интересуется его потоком информации.

Метод Subscribe возвращает объект, реализующий интерфейс IDisposable. Затем этот объект может быть использован Observer для отказа от подписки на поток информации, предоставленный Observable. Как только это будет сделано, наблюдатель не будет уведомлен ни о каких обновлениях потока информации.

IObserver‹в Т›

Это контравариантный интерфейс, представляющий любого наблюдателя. Если вы хотите узнать больше о дисперсии в .NET, вы можете прочитать статью Ковариация и контравариантность в .NET C#.

Члены, определенные в этом интерфейсе:

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

Метод OnCompleted должен быть вызван Observable, чтобы сообщить Observer, что поток информации завершен и Observerне должен ожидать больше информации. .

Метод OnError должен вызываться Observable, чтобы сообщить Observer, что произошла ошибка.

Observable должен вызывать метод OnNext, чтобы сообщить Observer, что новый фрагмент информации готов и добавляется в поток.

Реализация Microsoft

Теперь давайте посмотрим, как Microsoft рекомендует реализовать шаблон проектирования Observer в C#. Позже я покажу вам некоторые небольшие улучшения, которые я реализовал сам.

Мы создадим простое консольное приложение прогноза погоды. В этом приложении у нас будет модуль WeatherForecast (Observable, Provider, Subject) и модуль WeatherForecastObserver (Observer).

Итак, приступим к реализации.

Информация о погоде

Это объект, представляющий часть информации, которая должна передаваться в информационном потоке.

Прогноз погоды

Что мы можем здесь заметить:

  1. Класс WeatherForecast реализует IObservable<WeatherInfo>.
  2. В реализации метода Subscribe мы проверяем, был ли переданный в Observer уже зарегистрирован ранее или нет. Если нет, мы добавляем его в список локальных m_Observers наблюдателей. Затем мы зацикливаемся на всех WeatherInfo записях, которые у нас есть в локальном списке m_WeatherInfoList, одну за другой, и сообщаем об этом наблюдателю, вызывая метод OnNext наблюдателя.
  3. Наконец, мы возвращаем новый экземпляр класса WeatherForecastUnsubscriber, который будет использоваться наблюдателем для отказа от подписки на информационный поток.
  4. Метод RegisterWeatherInfo определен таким образом, чтобы главный модуль мог регистрировать новые WeatherInfo. В реальном мире это может быть заменено внутренним запланированным вызовом API или прослушивателем SignalR Hub или чем-то еще, что будет служить источником информации.

Отписаться‹T›

Что мы можем здесь заметить:

  1. Это базовый класс для любого отписавшегося.
  2. Он реализует IDisposable, применяя одноразовый шаблон проектирования.
  3. Через конструктор он принимает полный список наблюдателей и наблюдателя, для которого он создан.
  4. При удалении он проверяет, существует ли уже наблюдатель в полном списке наблюдателей. Если да, он удаляет его из списка.

ПогодаПрогнозОтписаться

Что мы можем здесь заметить:

  1. Это наследуется от класса Unsubscriber<T>.
  2. Особой обработки не происходит.

ПогодаПрогнозОбозреватель

Что мы можем здесь заметить:

  1. Класс WeatherForecastObserver реализует IObserver<WeatherInfo>.
  2. На методе OnNext пишем температуру в консоль.
  3. По методу OnCompleted пишем в консоль «Completed».
  4. На методе OnError пишем в консоль «Ошибка».
  5. Мы определили метод void Subscribe(WeatherForecast provider), чтобы позволить основному модулю инициировать процесс регистрации. Возвращенный объект отказа от подписки сохраняется внутри для использования в случае отказа от подписки.
  6. Используя ту же концепцию, определен метод void Unsubscribe(), который использует внутренне сохраненный объект отказа от подписки.

Программа

Что мы можем здесь заметить:

  1. Мы создали экземпляр провайдера.
  2. Потом зарегистрировал 3 штуки инфы.
  3. До этого момента в консоль ничего не должно записываться, так как наблюдатели не определены.
  4. Затем создал экземпляр наблюдателя.
  5. Затем подписал наблюдателя на поток.
  6. На данный момент мы должны найти 3 зарегистрированных значения температуры в консоли. Это связано с тем, что когда наблюдатель подписывается, он получает уведомление об уже существующей информации, а в нашем случае это 3 части информации.
  7. Затем мы регистрируем 2 части информации.
  8. Итак, мы получаем еще 2 сообщения, выведенные на консоль.
  9. Потом отписываемся.
  10. Затем мы регистрируем 1 часть информации.
  11. Однако эта часть информации не будет записана в консоль, поскольку наблюдатель уже отписался.
  12. Затем наблюдатель снова подписывается.
  13. Затем мы регистрируем 1 часть информации.
  14. Итак, эта часть информации записывается в консоль.

Наконец, выполнение этого должно привести к такому результату:

Моя расширенная реализация

Когда я проверил реализацию Microsoft, я обнаружил некоторые проблемы. Поэтому я решил внести небольшие изменения.

IExtendedObservable‹out T›

Что мы можем здесь заметить:

  1. Интерфейс IExtendedObservable<out T> расширяет интерфейс IObservable<T>.
  2. Это ковариантный. Если вы хотите узнать об этом больше, вы можете прочитать статью Ковариантность и контравариантность в .NET C#.
  3. Мы определили свойство IReadOnlyCollection<T> Snapshot, чтобы позволить другим модулям мгновенно получать список уже существующих информационных записей без необходимости подписываться.
  4. Мы также определили метод IDisposable Subscribe(IObserver<T> observer, bool withHistory) с дополнительным параметром bool withHistory, чтобы наблюдатель мог решить, хочет ли он получать уведомления об уже существующих информационных записях или нет в момент подписки.

Отписаться

Что мы можем здесь заметить:

  1. Теперь класс Unsubscriber не является универсальным.
  2. Это потому, что ему больше не нужно знать тип информационного объекта.
  3. Вместо того, чтобы иметь доступ к полному списку наблюдателей и наблюдателю, для которого он создан, он просто уведомляет Observable, когда он удаляется, и Observable сам обрабатывает процесс отмены регистрации.
  4. Таким образом, он делает меньше, чем раньше, и выполняет только свою работу.

ПогодаПрогнозОтписаться

Что мы можем здесь заметить:

  1. Мы удалили часть <T> из Unsubscriber<T>.
  2. И теперь конструктор принимает Action, который будет вызываться в случае удаления.

Прогноз погоды

Что мы можем здесь заметить:

  1. Это почти то же самое, за исключением свойства IReadOnlyCollection<WeatherInfo> Snapshot, которое возвращает внутренний список m_WeatherInfoList, но как IReadOnlyCollection.
  2. И метод IDisposable Subscribe(IObserver<WeatherInfo> observer, bool withHistory), использующий параметр withHistory.

ПогодаПрогнозОбозреватель

Что мы можем заметить здесь, так это то, что это почти то же самое, за исключением Subscribe(WeatherForecast provider), который теперь решает, следует ли ему Subscribe с историей или нет.

Программа

Это то же самое, что и раньше.

Наконец, выполнение этого должно привести к тому же результату, что и раньше:

Что дальше

Теперь вы знаете основы шаблона проектирования Observer в .NET C#. Однако это не конец истории.

Существуют библиотеки, созданные поверх интерфейсов IObservable<T> и IObserver<T>, предоставляющие более интересные функции и возможности, которые могут оказаться полезными.

Одной из этих библиотек является библиотека Reactive Extensions for .NET (Rx). Он состоит из набора методов расширения и стандартных операторов последовательности LINQ для поддержки асинхронного программирования.

Поэтому я призываю вас изучить эти библиотеки и попробовать их. Я уверен, что вы хотели бы некоторые из них.

Надеюсь, вы нашли этот контент полезным. Если вы хотите поддержать:

▶ Если вы еще не являетесь участником Medium, вы можете использовать мою реферальную ссылку, чтобы я мог получать часть ваших сборов от Medium. > вы ничего не платите.
▶ Подпишитесь на мою рассылку новостей, чтобы получать рекомендации, руководства, подсказки, подсказки и многое другое прямо на ваш почтовый ящик.

Другие источники

Это другие ресурсы, которые могут оказаться полезными.