Observer (Наблюдатель)¶
Категория: паттерн поведения.
Проблема¶
Одному событию в системе (изменилась цена акции, обновились данные, что-то пошло не так) должны отреагировать несколько независимых частей системы - логирование, UI, отправка алертов, кеш. При этом источник события (субъект) не должен: - знать заранее, сколько подписчиков и какие именно; - быть жёстко связанным с конкретными классами подписчиков (иначе при добавлении нового подписчика пришлось бы менять код субъекта).
Решение¶
- Субъект хранит список подписчиков (наблюдателей) и предоставляет способ подписаться/отписаться.
- При изменении состояния субъект уведомляет всех подписчиков, вызывая у каждого условный метод
Update/обработчик события. - Подписчики реализуют этот метод по-своему, реагируя на уведомление независимо друг от друга.
- Субъект работает с подписчиками через общую абстракцию (интерфейс или делегат) и не знает о конкретных типах подписчиков.
Варианты реализации в .NET¶
- События (
event) и делегаты - идиоматичный для C# способ реализовать Observer:event EventHandler<T>в субъекте, методы-обработчики у подписчиков,+=/-=для подписки/отписки. Это встроенный в язык вариант паттерна, показанный в примере выше. - Методы обратного вызова (callbacks) - более простой, "ручной" вариант без выделенного механизма
event, например поле типаAction<T>. - Строго типизированный интерфейс наблюдателя (
IObserver/собственный интерфейс с методомNotify) - когда наблюдателей нужно хранить и явно перебирать как объекты (например, чтобы вызывать доп. методы, а не только обработку уведомления). IObservable<T>/IObserver<T>и Reactive Extensions (Rx) - развитая версия паттерна с поддержкой завершения потока событий (OnCompleted), ошибок (OnError) и богатым набором операторов для комбинирования потоков событий.
Структура¶
Subject- хранит состояние, список наблюдателей, методы подписки/отписки и уведомления.Observer- интерфейс/делегат с методом реакции на уведомление.ConcreteObserver- конкретная реализация реакции на событие.
Когда применять¶
- Изменение состояния одного объекта должно приводить к обновлению произвольного числа других объектов, заранее не известных субъекту.
- Нужно избежать жёсткой связи между источником события и его обработчиками, чтобы подписчиков можно было добавлять/убирать без изменения субъекта.
Плюсы¶
- Слабая связанность: субъект и наблюдатели знают только про общий контракт уведомления.
- Подписчиков можно добавлять и убирать в рантайме, не трогая код субъекта.
- Хорошо ложится на событийно-ориентированную архитектуру (UI, доменные события).
Минусы¶
- Порядок вызова обработчиков обычно не гарантирован и не должен быть значимым - если он важен, Observer не лучший инструмент.
- Утечки памяти: если подписчик не отписывается от
eventдолгоживущего субъекта, субъект держит на него ссылку и не даёт собрать сборщику мусора (частая проблема в .NET, особенно в UI-приложениях). - Цепочки уведомлений (наблюдатель сам является субъектом для других наблюдателей) могут привести к сложно отслеживаемым каскадным обновлениям.
Пример в .NET Framework / BCL¶
event/EventHandler- механизм событий встроен в CLR как часть языка именно ради этого паттерна.INotifyPropertyChanged/INotifyCollectionChanged- основа биндинга данных в WPF, MAUI, Blazor.IObservable<T>/IObserver<T>изSystemи библиотека Reactive Extensions (Rx.NET).FileSystemWatcher- уведомляет подписчиков об изменениях в файловой системе через события.