Назначение
Организует уведомление нескольких подписчиков, заинтересованных в отслеживании изменения состояния компонента, путем регистрации подписчиков в компоненте. Шаблон реализует механизм организации уведомлений по способу push. Он заключается в том что компонент сам оповещает слушателей, когда случается изменение. Способ часто противопоставляют poll. Poll заключается в опросе источника изменений с некоторой периодичностью: “сейчас есть изменения?”. Этот способ сильно нагружает систему и нагрузка существенно возрастает, когда добавляется больше подписчиков.
- изменения в одном компоненте вызывают изменения в некотором множестве других объектов, которое может меняться динамически
- источник изменений не должен зависеть от того, какие конкретно ещё объекты заинтересованы в отслеживании изменений
Описание
Шаблон Observer применяют когда есть один объект, который является источником изменений, интересных для других объектов.
- Observable - источник изменений
- Observer - объект, заинтересованный в получении уведомлений об изменении состояния Observable
Для того чтобы начать получение уведомлений о состоянии Observable объекта, подписчики регистрируются в нем через метод, объявленный в интерфейсе Observable. Observable ответственен за хранение и актуализацию списка подписчиков. В этом смысле, шаблон Observable нарушает принцип Single Responsibility Principle, так как он вынужден отвечать за две функциональности одновременно: свою обычную функциональность и функциональность, связанную с обслуживанием подписчиков. При изменении состояния Observable объекта он в цикле вызывает специальный метод из интерфейса Observer, который сигнализирует подписчикам о событии.
Проблемы:
- Вызов подписчиков обычно не упорядочен;
- Надо избегать циклической передачи события от объекта к самому себе/home/eremeykin/Downloads/observer-class-diagram-example.png;
- Код обработки у подписчиков не должен содержать затратные по времени операции, их надо делать асинхронно, вынося в отдельный поток.
Реализация
update(:Context)
, см. Варианты);update()
;update()
не имеет параметров, конкретный приёмник событий принимает конкретный источник через конструктор и обращается к нему для вытягивания информации о том что конкретно произошло.Примеры
Имеется станция для наблюдения за погодой, которая в частности, может предоставлять данные о текущей температуре. Необходимо отображать текущую температуру на дисплеях разного вида: это может быть экран смартфона или оконный LCD дисплей.
Погодная станция является источником событий изменения температуры, в получении которых
заинтересованы подписчики. Подписчики при получении сигнала об изменении температуры,
вычитывают текущее значение station.getTemperature()
и отрисовывают новое значение.
Варианты
-
Передача контекста. Единственный метод в интерфейсе Observer может быть объявлен без параметров или с параметрами, через которые передаются некоторые данные о том, что поменялось в Observable. Например, через параметр может быть передан аргумент события, свойства которого отражают изменения (контекст). Этот сценарий можно назвать push-push, он не требует хранения ссылки на Observable внутри Observer объектов. Другой вариант – сделать метод без параметров, но в Observer хранить ссылку на вызывающий метод объект. В таком случае можно говорить о сценарии push-pull: информация о факте изменения передается по схеме push, а данные об этом изменении – по схеме pull.
Сигнатура метода Факт изменения Данные Нужна ссылка Observable ⬅ Observer update(:Context)
push push нет update()
push pull да - Multicaster. Функционал оповещения и хранения подписчиков может быть вынесен из конкретного класса
Observable для того чтобы переиспользовать этот функционал. Например, Observable может делегировать
вызовы
addObserver()
removeObserver()
notifyObservers()
специальному классуMulticaster
. Также функционал можно вынести в абстрактный класс, но следует помнить, что для наследования есть только один слот, а интерфейсов можно реализовать много. - Batching. Можно реализовать уведомления батчами. В этом случае уведомления выталкиваются не сразу, а накапливаются в коллекции до определенного времени (либо до заполнения коллекции, либо до тех пор, пока батч не будет вытолкнут извне)
- Veto. Можно реализовать схему, при которой подписчики запрещают проводить компоненту изменение. Компонент отправляет уведомления до события и подписчики могут запретить проведение этого изменения, например, выкинув исключение.
- Метод
notifyObservers()
не всегда имеет смысл делатьpublic
. - Иногда можно упростить шаблон и не делать интерфейс
Observable
, функционал подписки и уведомлений разместить в конкретном классе, см. refactoring.guru.
Чем отличается
Bridge похож структурно на Observer. Оба шаблона имеют две иерархии наследования, одна из которых имеет ссылку на другую. В случае Bridge иерархия Abstraction часто представлена абстрактным классом. Bridge, как правило, имеет жесткую связь один к одному, в то время как в Observer коллекция динамически изменяется и может содержать множество объектов.
Strategy можно сказать в некотором смысле общий случай Observer. Способ реагирования на измененное состояние наблюдаемого объекта можно назвать стратегией.
Adapter может быть применен совместно с Observer. Например, некоторый бизнес объект не реализует интерфейс Observer, но должен получать уведомления об изменении наблюдаемого объекта. Тогда через адаптер можно приспособить бизнес объект к интерфейсу Observer.
Command Мы знаем, что Command - готовая к выполнению
команда, содержащая внутри себя все необходимые данные, чтобы выполнить её вызовом
метода без аргументов. То есть если через Adapter мы приведем вызов бизнес-объекта
к вызову update()
, то, можно сказать что этот Adapter является Command. Конечно,
это команда без функции отмены.
Ссылки
https://java-design-patterns.com/patterns/observer/
https://github.com/iluwatar/java-design-patterns/tree/master/observer