Образец наблюдателя - Observer pattern

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

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

Обзор

Шаблон проектирования Observer - один из двадцати трех хорошо известных Паттерны проектирования "Банда четырех" описание того, как решать повторяющиеся проблемы проектирования, чтобы разрабатывать гибкое и многократно используемое объектно-ориентированное программное обеспечение, то есть объекты, которые легче реализовать, изменить, протестировать и повторно использовать.[1]

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

Паттерн Observer решает следующие проблемы:[2]

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

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

Какое решение описывает шаблон проектирования Observer?

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

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

См. Также схему классов и последовательности UML ниже.

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

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

Связь и типичные реализации pub-sub

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

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

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

Связанные шаблоны: Шаблон публикации – подписки, посредник, одиночка.

Несвязанный

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

Структура

Схема классов и последовательности UML

Пример класса UML и диаграмма последовательности для шаблона проектирования Observer. [6]

В приведенном выше UML диаграмма классов, то Предмет класс не обновляет состояние зависимых объектов напрямую. Предмет относится к Наблюдатель интерфейс (Обновить()) для обновления состояния, что делает Предмет независимо от того, как обновляется состояние зависимых объектов. Наблюдатель1 и Наблюдатель2 классы реализуют Наблюдатель интерфейс, синхронизируя их состояние с состоянием субъекта.

В UML схема последовательности показывает взаимодействия во время выполнения: Наблюдатель1 и Наблюдатель2 объекты называют прикрепить (это) на Тема 1 зарегистрироваться. Предполагая, что состояние Тема 1 изменения,Тема 1 звонки уведомлять() на себя.
уведомлять() звонки Обновить() на зарегистрированных Наблюдатель1 и Наблюдатель2объекты, запрашивающие измененные данные (getState ()) от Тема 1 обновлять (синхронизировать) их состояние.

Диаграмма классов UML

UML диаграмма классов паттерна Observer

пример

Пока классы библиотеки java.util.Observer и java.util.Observable существуют, они устарели в Java 9, потому что реализованная модель была весьма ограниченной.

Ниже приведен пример, написанный на Ява который принимает ввод с клавиатуры и обрабатывает каждую строку ввода как событие. Когда строка передается из System.in, метод notifyObservers затем вызывается, чтобы уведомить всех наблюдателей о возникновении события в форме вызова их методов «обновления».

Ява

импорт java.util.List;импорт java.util.ArrayList;импорт java.util.Scanner;класс EventSource {    общественный интерфейс Наблюдатель {        пустота Обновить(Строка мероприятие);    }      частный окончательный Список<Наблюдатель> наблюдатели = новый ArrayList<>();      частный пустота notifyObservers(Строка мероприятие) {        наблюдатели.для каждого(наблюдатель -> наблюдатель.Обновить(мероприятие)); // альтернативное лямбда-выражение: Observers.forEach (Observer :: update);    }      общественный пустота addObserver(Наблюдатель наблюдатель) {        наблюдатели.Добавить(наблюдатель);    }      общественный пустота scanSystemIn() {        Сканер сканер = новый Сканер(Система.в);        в то время как (сканер.hasNextLine()) {            Строка линия = сканер.nextLine();            notifyObservers(линия);        }    }}
общественный класс ObserverDemo {    общественный статический пустота основной(Строка[] аргументы) {        Система.вне.println("Введите текст:");        EventSource eventSource = новый EventSource();                eventSource.addObserver(мероприятие -> {            Система.вне.println("Получен ответ:" + мероприятие);        });        eventSource.scanSystemIn();    }}

Groovy

класс EventSource {    частный наблюдатели = []    частный notifyObservers(Строка мероприятие) {        наблюдатели.каждый { Это(мероприятие) }    }    пустота addObserver(наблюдатель) {        наблюдатели += наблюдатель    }    пустота scanSystemIn() {        вар сканер = новый Сканер(Система.в)        в то время как (сканер) {            вар линия = сканер.nextLine()            notifyObservers(линия)        }    }}println "Введите текст:"вар eventSource = новый EventSource()eventSource.addObserver { мероприятие ->    println "Получен ответ: $ event"}eventSource.scanSystemIn()

Котлин

импорт java.util.Scannertypealias Наблюдатель = (мероприятие: Строка) -> Единица измерения;класс EventSource {    частный вал наблюдатели = mutableListOf<Наблюдатель>()    частный весело notifyObservers(мероприятие: Строка) {        наблюдатели.для каждого { Это(мероприятие) }    }    весело addObserver(наблюдатель: Наблюдатель) {        наблюдатели += наблюдатель    }    весело scanSystemIn() {        вал сканер = Сканер(Система.`in`)        в то время как (сканер.hasNext()) {            вал линия = сканер.nextLine()            notifyObservers(линия)        }    }}
весело основной(аргумент: Список<Строка>) {    println("Введите текст:")    вал eventSource = EventSource()    eventSource.addObserver { мероприятие ->        println("Получен ответ: $ event")    }    eventSource.scanSystemIn()}

Delphi

использует  Система.Дженерики.Коллекции  , Система.SysUtils  ;тип  IObserver = интерфейс    ['{0C8F4C5D-1898-4F24-91DA-63F1DD66A692}']    процедура Обновить(const Ценность: строка);  конец;тип  TEdijsObserverManager = класс  строгий частный    FObservers: TList<IObserver>;  общественный    конструктор Создайте; перегрузка;    деструктор Уничтожить; отменять;    процедура NotifyObservers(const Ценность: строка);    процедура AddObserver(const AObserver: IObserver);    процедура Отменить регистрацию(const AObserver: IObserver);  конец;тип  TListener = класс(TInterfacedObject, IObserver)  строгий частный    FName: строка;  общественный    конструктор Создайте(const Имя: строка); повторно ввести;    процедура Обновить(const Ценность: строка);  конец;процедура TEdijsObserverManager.AddObserver(const AObserver: IObserver);начать  если не FObservers.Содержит(AObserver) тогда    FObservers.Добавить(AObserver);конец;начать  FreeAndNil(FObservers);  унаследованный;конец;процедура TEdijsObserverManager.NotifyObservers(const Ценность: строка);вар  я: Целое число;начать  для я := 0 к FObservers.Считать - 1 делать    FObservers[я].Обновить(Ценность);конец;процедура TEdijsObserverManager.Отменить регистрацию(const AObserver: IObserver);начать  если FObservers.Содержит(AObserver) тогда    FObservers.удалять(AObserver);конец;конструктор TListener.Создайте(const Имя: строка);начать  унаследованный Создайте;  FName := Имя;конец;процедура TListener.Обновить(const Ценность: строка);начать  WriteLn(FName + 'слушатель получил уведомление:' + Ценность);конец;процедура TEdijsForm.ObserverExampleButtonClick(Отправитель: TObject);вар  _DoorNotify: TEdijsObserverManager;  _ListenerHusband: IObserver;  _ListenerWife: IObserver;начать  _DoorNotify := TEdijsObserverManager.Создайте;  пытаться    _ListenerHusband := TListener.Создайте('Муж');    _DoorNotify.AddObserver(_ListenerHusband);    _ListenerWife := TListener.Создайте('Жена');    _DoorNotify.AddObserver(_ListenerWife);    _DoorNotify.NotifyObservers('Кто-то стучится в дверь');  Ну наконец то    FreeAndNil(_DoorNotify);  конец;конец;

Вывод

Слушатель мужа получил уведомление: Кто-то стучит в дверь Слушатель мужа получил уведомление: Кто-то стучится в дверь

Python

Аналогичный пример в Python:

класс Наблюдаемый:    def __в этом__(я) -> Никто:        я._observers = []        def register_observer(я, наблюдатель) -> Никто:        я._observers.добавить(наблюдатель)        def notify_observers(я, *аргументы, **kwargs) -> Никто:        для наблюдатель в я._observers:            наблюдатель.уведомлять(я, *аргументы, **kwargs)класс Наблюдатель:    def __в этом__(я, наблюдаемый) -> Никто:        наблюдаемый.register_observer(я)        def уведомлять(я, наблюдаемый, *аргументы, **kwargs) -> Никто:        Распечатать("Получил", аргументы, kwargs, "Из", наблюдаемый)предмет = Наблюдаемый()наблюдатель = Наблюдатель(предмет)предмет.notify_observers("тест")

C #

    общественный класс Полезная нагрузка    {        общественный строка Сообщение { получать; набор; }    }
    общественный класс Предмет : IObservable<Полезная нагрузка>    {        общественный IList<IObserver<Полезная нагрузка>> Наблюдатели { получать; набор; }        общественный Предмет()        {            Наблюдатели = новый Список<IObserver<Полезная нагрузка>>();        }        общественный IDisposable Подписаться(IObserver<Полезная нагрузка> наблюдатель)        {                     если (!Наблюдатели.Содержит(наблюдатель))            {                Наблюдатели.Добавить(наблюдатель);            }            вернуть новый Отписавшийся от подписки(Наблюдатели, наблюдатель);        }        общественный пустота Отправить сообщение(строка сообщение)        {            для каждого (вар наблюдатель в Наблюдатели)            {                наблюдатель.OnNext(новый Полезная нагрузка { Сообщение = сообщение });            }        }    }
    общественный класс Отписавшийся от подписки : IDisposable    {        частный IObserver<Полезная нагрузка> наблюдатель;        частный IList<IObserver<Полезная нагрузка>> наблюдатели;        общественный Отписавшийся от подписки(IList<IObserver<Полезная нагрузка>> наблюдатели, IObserver<Полезная нагрузка> наблюдатель)        {            этот.наблюдатели = наблюдатели;            этот.наблюдатель = наблюдатель;        }        общественный пустота Утилизировать()        {            если (наблюдатель != значение NULL && наблюдатели.Содержит(наблюдатель))            {                наблюдатели.удалять(наблюдатель);            }        }    }
    общественный класс Наблюдатель : IObserver<Полезная нагрузка>    {        общественный строка Сообщение { получать; набор; }        общественный пустота OnCompleted()        {        }        общественный пустота OnError(Исключение ошибка)        {        }        общественный пустота OnNext(Полезная нагрузка ценность)        {            Сообщение = ценность.Сообщение;        }        общественный IDisposable регистр(Предмет предмет)        {            вернуть предмет.Подписаться(этот);        }    }

JavaScript

Для JavaScript существуют библиотеки и фреймворки, использующие шаблон наблюдателя. Одна из таких библиотек RxJS видно ниже.

// импортируем оператор fromEventимпорт { fromEvent } от 'rxjs';// получить ссылку на кнопкуconst кнопка = документ.getElementById('myButton');// создаем наблюдаемое нажатие кнопокconst myObservable = fromEvent(кнопка, 'щелкнуть');// пока давайте просто записываем событие при каждом нажатииconst подписка = myObservable.подписываться(мероприятие => консоль.журнал(мероприятие));

Смотрите также

использованная литература

  1. ^ Эрих Гамма; Ричард Хелм; Ральф Джонсон; Джон Влиссидес (1994). Паттерны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования. Эддисон Уэсли. стр.293ff. ISBN  0-201-63361-2.
  2. ^ «Шаблон проектирования Observer - проблема, решение и применимость». w3sDesign.com. Получено 2017-08-12.
  3. ^ Сравнение различных реализаций паттернов наблюдателя Моше Биндлер, 2015 (Github)
  4. ^ Различия между шаблоном pub / sub и наблюдателем Образец наблюдателя от Ади Османи (Интернет-книги о сафари)
  5. ^ Опыт программирования в Windows Чарльз Петцольд, 10 ноября 1992 г., Журнал ПК (Google Книги )
  6. ^ «Шаблон проектирования Observer - структура и взаимодействие». w3sDesign.com. Получено 2017-08-12.

внешние ссылки