Паттерны проектирования
5. Паттерн «Наблюдатель» (Observer)
Существует два способа общения между двумя программными элементами.
Компонент 1 может обратиться к Компоненту 2 для получения каких-то данных или выполнения некоторой операции, либо Компонент 2 может уведомить Компонент 1 о некотором событии. Первая модель взаимодействия называется pull-моделью, а вторая — push-моделью. В мире ООП push-модель реализуется с помощью паттерна «Наблюдатель».Назначение: определяет зависимость типа «один ко многим» между объектами таким образом, что при изменении состояния одного объекта все зависящие от него оповещаются об этом и автоматически обновляются.
Причины использования:
При проектировании некоторого класса у разработчика всегда есть несколько вариантов реализации. Класс
А может знать о существовании класса Б и уведомлять его о произошедшем событии. Или же класс А может быть наблюдаемым и уведомлять о некотором событии всех заинтересованных подписчиков. Использование наблюдателей уменьшает связанность между классами/модулями и упрощает повторное использование. Благодаря слабой связанности наблюдаемый объект и наблюдатели могут располагаться на разных уровнях абстракции или слоях приложения. Например, слой пользовательского интерфейса знает о модели, но модель не должна ничего знать о пользовательском интерфейсе.Классическая диаграмма приведена на рисунке ниже:
-
Observer — определяет интерфейс наблюдателя;-
Subject (наблюдаемый объект) — определяет методы подключения и отключения наблюдателей;-
ConcreteObserver — реализует интерфейс наблюдателя;-
ConcreteSubject — конкретный тип наблюдаемого объекта.На платформе .NET практически невозможно встретить классическую реализацию паттерна «Наблюдатель». Существует несколько вариантов реализации:
1. С помощью делегатов (методов обратного вызова).
Самая простая форма наблюдателя. Для этого достаточно, чтобы класс потребовал делегат в аргументах конструктора и уведомлял вызывающий код с его помощью. Это позволяет гарантировать наличие наблюдателя, а также наблюдаемый объект может не только уведомлять об изменении своего состояния, но и требовать от делегата некоторого результата.
2. С помощью событий (events).
События представляют собой умную оболочку над делегатами, которая позволяет клиентам лишь подписываться на события или отказываться от подписки, а владельцу события — инициировать событие для уведомления всех подписчиков. Разница с первым вариантом в том, что интерфейс наблюдаемого объекта позволяет подписаться на событие любому числу подписчиков. При этом нет гарантии, что эти подписчики вообще будут. Подробнее о событиях
3. С помощью специализированных интерфейсов-наблюдателей.
В некоторых случаях, когда имеется несколько событий или делегатов, их удобно объединить в одном интерфейсе. Данный вариант очень похож на классическую версию паттерна «Наблюдатель», хотя обычно наблюдатель является единственным.
4. С помощью интерфейсов IObserver/IObservable.
Все перечисленные ранее варианты реализации паттерна «Наблюдатель» содержат одно ограничение: они плохо объединяются для получения более высокоуровневого поведения (not composable). Над событиями или делегатами невозможно выполнять операции, которые можно выполнять над последовательностями. С 4-й версии в .NET Framework появилась пара интерфейсов
IObserver/IObservable с набором методов расширений, известных под названием реактивных расширений (Rx, Reactive Extensions). Интерфейс IObservable моделирует реактивные последовательности и позволяет работать с наблюдаемыми последовательностями через привычный LINQ-синтаксис. Он предполагает, что однородные события будут периодически повторяться. Наблюдаемый объект может уведомить о новом событии (OnNext), о том, что в процессе события произошла ошибка (OnError), или о том, что цепочка событий завершена (OnComplete). Пример: поток сетевых сообщений от клиента или сервера, координаты устройства и т.п.Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 5.