Observer

Назначение

Организует уведомление нескольких подписчиков, заинтересованных в отслеживании изменения состояния компонента, путем регистрации подписчиков в компоненте. Шаблон реализует механизм организации уведомлений по способу push. Он заключается в том что компонент сам оповещает слушателей, когда случается изменение. Способ часто противопоставляют poll. Poll заключается в опросе источника изменений с некоторой периодичностью: “сейчас есть изменения?”. Этот способ сильно нагружает систему и нагрузка существенно возрастает, когда добавляется больше подписчиков.

  • изменения в одном компоненте вызывают изменения в некотором множестве других объектов, которое может меняться динамически
  • источник изменений не должен зависеть от того, какие конкретно ещё объекты заинтересованы в отслеживании изменений

Описание

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

  • Observable - источник изменений
  • Observer - объект, заинтересованный в получении уведомлений об изменении состояния Observable

Для того чтобы начать получение уведомлений о состоянии Observable объекта, подписчики регистрируются в нем через метод, объявленный в интерфейсе Observable. Observable ответственен за хранение и актуализацию списка подписчиков. В этом смысле, шаблон Observable нарушает принцип Single Responsibility Principle, так как он вынужден отвечать за две функциональности одновременно: свою обычную функциональность и функциональность, связанную с обслуживанием подписчиков. При изменении состояния Observable объекта он в цикле вызывает специальный метод из интерфейса Observer, который сигнализирует подписчикам о событии.

Проблемы:

  • Вызов подписчиков обычно не упорядочен;
  • Надо избегать циклической передачи события от объекта к самому себе/home/eremeykin/Downloads/observer-class-diagram-example.png;
  • Код обработки у подписчиков не должен содержать затратные по времени операции, их надо делать асинхронно, вынося в отдельный поток.

Реализация

Observable
Observable
Интерфейс, предоставляющий возможность подписки на обновления интересующего объекта;
Observer
Observer
Интерфейс подписчика, через который объект-источник изменений может уведомить о факте изменения (и контексте, если метод объявлен с параметром update(:Context), см. Варианты);
ConcreteObservable
WeatherStation
Конкретный источник событий, который регистрирует подписчиков на определенное событие изменения состояния. Когда некоторые бизнес-процессы приводят к смене состояния, ConcreteObservable уведомляет об этом всех зарегистрированных подписчиков Observer путем вызова у них метода update();
ConcreteObserver
PhoneDisplay
Конкретный приёмник событий, обрабатывает уведомление о смене состояния наблюдаемого объекта. В случае, если метод 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

https://refactoring.guru/design-patterns/observer

Baeldung - The Observer Pattern in Java