Паттерн наблюдатель (Observer)

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

Он в основном используется для реализации распределенных систем обработки событий в программном обеспечении, "управляемом событиями" (event driven). В этих системах субъект обычно называется "потоком событий" или "источником событий", в то время как наблюдатели называются "приемниками событий". Номенклатура потока имитирует или адаптируется к физической установке, где наблюдатели физически разделены и не контролируют испускаемые события субъекта/источника потока. Этот паттерн идеально подходит для любого процесса, где данные поступают через ввод/вывод, то есть когда данные недоступны ЦПУ при запуске, но могут поступать "случайно" (HTTP-запросы, данные GPIO, ввод пользователя с клавиатуры/мыши/..., распределенные базы данных и блокчейны, ...). Большинство современных языков имеют встроенные конструкции "события", которые реализуют компоненты паттерна наблюдателя. Хотя это и не обязательно, большинство реализаций "наблюдателей" будут использовать фоновые потоки, прослушивающие предметные события и другие механизмы поддержки из ядра (Linux epoll, ...)

Паттерн проектирования Наблюдатель (Observer) - это один из двадцати трех известных шаблонов проектирования "Банды четырех" ("Gang of Four"), которые описывают, как решать повторяющиеся проблемы проектирования для разработки гибкого и многократно используемого объектно-ориентированного программного обеспечения, то есть объектов, которые легче внедрять, изменять, тестировать и использовать повторно.

Какие проблемы может решить паттерн проектирования Наблюдатель?

Паттерн Наблюдатель решает следующие проблемы:

  • Зависимость один-ко-многим между объектами должна быть определена без того, чтобы объекты были тесно связаны.
  • Следует обеспечить, чтобы при изменении состояния одного объекта набор зависимых объектов обновлялся автоматически.
  • Должно быть возможно, что один объект может уведомить неограниченное количество других объектов.

Определение зависимости один-ко-многим между объектами путем определения одного объекта (субъекта), который непосредственно обновляет состояние зависимых объектов, является негибким, поскольку он связывает субъект с конкретными зависимыми объектами. Тем не менее, это может иметь смысл с точки зрения производительности или, если реализация объекта тесно связана (подумайте о структурах ядра низкого уровня, которые выполняются тысячи раз в секунду). В некоторых сценариях сложно реализовать тесно связанные объекты, и их можно использовать повторно, поскольку они ссылаются и знают (как обновить) множество различных объектов с разными интерфейсами. В других сценариях тесно связанные объекты могут быть лучшим вариантом, так как компилятор сможет обнаруживать ошибки во время компиляции и оптимизировать код на уровне команд ЦП.

Какое решение описывает паттерн проектирования Наблюдатель?

  • Определите объекты Субъект и Наблюдатель.
  • так что, когда субъект меняет состояние, все зарегистрированные наблюдатели уведомляются и обновляются автоматически (и, вероятно, асинхронно).

Единственная ответственность субъекта состоит в том, чтобы вести список наблюдателей и уведомлять их об изменениях состояния, вызывая их операцию update().

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

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

Это взаимодействие уведомления-регистрации также называется публикацией-подпиской.

Сильная и слабая ссылка

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

Соединение и типичные реализации pub-sub

Как правило, паттерн наблюдатель реализуется таким образом, что "наблюдаемый" субъект является частью объекта, для которого наблюдаются изменения состояния (и сообщается наблюдателям). Этот тип реализации считается "тесно связанным" ("tightly coupled"), заставляя наблюдателей и субъекта быть осведомленными друг о друге и иметь доступ к их внутренним частям, создавая возможные проблемы масштабируемости, скорости, восстановления и обслуживания сообщений (также называемые событием или потерей уведомления), отсутствие гибкости в условном рассеянии и возможные препятствия для желаемых мер безопасности. В некоторых (не опрашивающих) реализациях паттернах публикации-подписки (известного как паттерн pub-sub) это решается путем создания выделенного сервера "очереди сообщений" (а иногда и дополнительного объекта "обработчик сообщений") в качестве дополнительной стадии между наблюдателем и наблюдаемым объектом, таким образом, разъединяя компоненты. В этих случаях к серверу очереди сообщений обращаются наблюдатели с шаблоном наблюдателя, который "подписывается на определенные сообщения", зная только об ожидаемом сообщении (или, в некоторых случаях, нет), но ничего не зная о самом отправителе сообщения; отправитель также может ничего не знать о наблюдателях. Другие реализации паттерна публикации-подписки, которые достигают аналогичного эффекта уведомления и связи с заинтересованными сторонами, вообще не используют паттерн наблюдатель.

В ранних реализациях многооконных операционных систем, таких как OS/2 и Windows, термины "шаблон публикации-подписки" и "разработка программного обеспечения на основе событий" использовались в качестве синонима для паттерна наблюдатель.

Паттерн наблюдатель, как описано в книге GoF, является очень базовой концепцией и не направлен на устранение интереса к изменениям наблюдаемого "субъекта" или специальной логике, которую должен выполнять наблюдаемый "субъект" до или после уведомления наблюдателей. Шаблон также не касается записи, когда отправляются уведомления об изменениях, или гарантии того, что они принимаются. Эти проблемы обычно обрабатываются в системах очередей сообщений, в которых шаблон наблюдатель является лишь малой частью.

Несвязанные

Паттерн наблюдатель может использоваться при отсутствии публикации-подписки, как в случае, когда статус модели часто обновляется. Частые обновления могут привести к тому, что представление перестает отвечать на запросы (например, вызывая много вызовов перерисовки); такие наблюдатели должны вместо этого использовать таймер. Таким образом, вместо того, чтобы быть перегруженным сообщением об изменении, наблюдатель заставит представление представлять приблизительное состояние модели через регулярный интервал. Этот режим наблюдателя особенно полезен для индикаторов выполнения, где ход выполнения основной операции изменяется несколько раз в секунду.

Структура

На приведенной выше диаграмме классов UML класс Subject не обновляет состояние зависимых объектов напрямую. Вместо этого Subject ссылается на интерфейс Observer (update()) для обновления состояния, что делает Subject независимым от того, как обновляется состояние зависимых объектов. Классы Observer1 и Observer2 реализуют интерфейс Observer, синхронизируя их состояние с состоянием субъекта.

Диаграмма последовательности UML показывает взаимодействия во время выполнения: объекты Observer1 и Observer2 вызывают метод attach(this) для Subject1, чтобы зарегистрироваться. Предполагая, что состояние Subject1 изменяется, Subject1 вызывает notify() для себя.

notify() вызывает update() для зарегистрированных объектов Observer1 и Observer2, которые запрашивают измененные данные (getState()) у Subject1 для обновления (синхронизации) своего состояния.

Пример реализации паттерна наблюдатель (observer) на Java

import java.util.ArrayList;
import java.util.Scanner;

class EventProducer {
    public interface Observer {
        void update(String event);
    }
  
    private final List observers = new ArrayList<>();
  
    private void notifyObservers(String event) {
        observers.forEach(observer -> observer.update(event)); 
        // альтернативно можно использовать lambda выражение 
        // observers.forEach(Observer::update);
    }
  
    public void addObserver(Observer observer) {
        observers.add(observer);
    }
  
    public void scanSystemIn() {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            notifyObservers(line);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println("Введите текст: ");
        EventProducer eventProducer = new EventProducer();
        
        eventProducer.addObserver(event -> {
            System.out.println("Получен ответ: " + event);
        });

        eventProducer.scanSystemIn();
    }
}

Комментарии

Популярные сообщения из этого блога

Язык поисковых запросов в Graylog

Хэш-таблица: разрешение коллизий

Нормальные формы, пример нормализации в базе данных