В проектировании и разработке программного обеспечения шаблон наблюдателя — это шаблон проектирования программного обеспечения , в котором объект , называемый субъектом , поддерживает список своих зависимых объектов, называемых наблюдателями , и автоматически уведомляет их о любых изменениях состояния , обычно путем вызова одного из их методов .
Он часто используется для реализации распределенных систем обработки событий в программном обеспечении, управляемом событиями . В таких системах субъект обычно называют «потоком событий» или «источником потока событий», а наблюдателей называют «приемниками событий». Номенклатура потока намекает на физическую установку, в которой наблюдатели физически разделены и не имеют контроля над событиями, исходящими от субъекта/источника потока. Таким образом, этот шаблон подходит для любого процесса, при котором данные поступают с какого-либо входа, который недоступен для ЦП при запуске , а вместо этого поступает, казалось бы, случайным образом ( запросы HTTP , данные GPIO , пользовательский ввод от периферийных устройств и распределенных баз данных и т. д.).
Шаблон проектирования наблюдателя — это поведенческий шаблон, входящий в число 23 известных шаблонов проектирования «Банды четырех» , который решает повторяющиеся проблемы проектирования с целью разработки гибкого и многократно используемого объектно-ориентированного программного обеспечения, создавая объекты, которые легче реализовать, изменить и протестировать. и повторное использование. [1]
Шаблон наблюдателя решает следующие проблемы: [2]
Определение зависимости «один ко многим» между объектами путем определения одного объекта (субъекта), который напрямую обновляет состояние зависимых объектов, является негибким, поскольку оно связывает субъект с конкретными зависимыми объектами. Однако это может быть применимо с точки зрения производительности или если реализация объекта тесно связана (например, низкоуровневые структуры ядра, которые выполняются тысячи раз в секунду). В некоторых сценариях сложно реализовать тесно связанные объекты, и их нелегко использовать повторно, поскольку они ссылаются на множество объектов с разными интерфейсами и знают о них. В других сценариях тесно связанные объекты могут быть лучшим вариантом, поскольку компилятор способен обнаруживать ошибки во время компиляции и оптимизировать код на уровне инструкций ЦП.
Subject
и Observer
объекты.Единственная ответственность субъекта — вести список наблюдателей и уведомлять их об изменениях состояния путем вызова их update()
операции. В обязанности наблюдателей входит регистрация и отмена регистрации у субъекта (чтобы получать уведомления об изменениях состояния) и обновление своего состояния (чтобы синхронизировать свое состояние с состоянием субъекта) при получении уведомления. Это делает субъект и наблюдателей слабосвязанными. Субъект и наблюдатели не имеют явных знаний друг о друге. Наблюдателей можно добавлять и удалять независимо во время выполнения. Это взаимодействие уведомления и регистрации также известно как публикация-подписка .
Шаблон наблюдателя может вызвать утечку памяти , известную как проблема истекшего прослушивателя , поскольку в базовой реализации он требует как явной регистрации, так и явной отмены регистрации, как в шаблоне удаления , поскольку субъект содержит сильные ссылки на наблюдателей, поддерживая их активность. Этого можно избежать, если субъект имеет слабые связи с наблюдателями.
Обычно шаблон наблюдателя реализуется таким образом, что наблюдаемый субъект является частью объекта, изменения состояния которого наблюдаются (и сообщаются наблюдателям). Этот тип реализации считается тесно связанным , вынуждающим как наблюдателей, так и субъекта знать друг о друге и иметь доступ к их внутренним частям, создавая возможные проблемы масштабируемости , скорости, восстановления и обслуживания сообщений (также называемых потерей событий или уведомлений). , отсутствие гибкости в условном рассредоточении и возможные препятствия для желаемых мер безопасности. В некоторых ( без опроса ) реализациях шаблона публикации-подписки эта проблема решается путем создания выделенного сервера очереди сообщений (а иногда и дополнительного объекта обработчика сообщений) в качестве дополнительного этапа между наблюдателем и наблюдаемым объектом, тем самым отделяя компоненты. В этих случаях к серверу очереди сообщений обращаются наблюдатели с шаблоном наблюдателя, подписывающиеся на определенные сообщения и знающие (или не знающие в некоторых случаях) только об ожидаемом сообщении, ничего не зная при этом о самом отправителе сообщения; отправитель также может ничего не знать о наблюдателях. Другие реализации шаблона публикации-подписки, которые достигают аналогичного эффекта уведомления и связи с заинтересованными сторонами, не используют шаблон наблюдателя. [3] [4]
В ранних реализациях многооконных операционных систем, таких как OS/2 и Windows , термины «шаблон публикации-подписки» и «разработка программного обеспечения, управляемая событиями», использовались как синонимы шаблона наблюдателя. [5]
Шаблон наблюдателя, описанный в книге «Шаблоны проектирования» , представляет собой очень базовую концепцию и не направлен на устранение интереса к изменениям наблюдаемого субъекта или специальной логике, которую должен выполнять наблюдаемый субъект до или после уведомления наблюдателей. Шаблон также не касается записи уведомлений об изменениях или гарантии их получения. Эти проблемы обычно решаются в системах очередей сообщений, в которых шаблон наблюдателя играет лишь небольшую роль.
Связанные шаблоны включают публикацию-подписку, медиатор и синглтон .
Шаблон наблюдателя можно использовать при отсутствии публикации-подписки, например, когда статус модели часто обновляется. Частые обновления могут привести к тому, что представление перестанет отвечать на запросы (например, из-за вызова множества вызовов перерисовки ); вместо этого такие наблюдатели должны использовать таймер. Вместо того, чтобы перегружаться сообщением об изменении, наблюдатель будет заставлять представление представлять приблизительное состояние модели через регулярные промежутки времени. Этот режим наблюдения особенно полезен для индикаторов выполнения , в которых ход базовой операции часто меняется.
В этой диаграмме классов UML класс не обновляет состояние зависимых объектов напрямую. Вместо этого относится к интерфейсу ( ) для обновления состояния, что делает независящим от того, как обновляется состояние зависимых объектов. Классы и реализуют интерфейс, синхронизируя свое состояние с состоянием субъекта.Subject
Subject
Observer
update()
Subject
Observer1
Observer2
Observer
Диаграмма последовательности UML показывает взаимодействие во время выполнения: объекты и вызывают регистрацию себя. Предполагая, что состояние меняется, взывает к себе. обращается к зарегистрированным объектам и объектам, которые запрашивают измененные данные ( ) для обновления (синхронизации) своего состояния.Observer1
Observer2
attach(this)
Subject1
Subject1
Subject1
notify()
notify()
update()
Observer1
Observer2
getState()
Subject1
Хотя библиотечные классы java.util.Observer и java.util.Observable существуют, они устарели в Java 9, поскольку реализованная модель была весьма ограниченной.
Ниже приведен пример, написанный на Java , который принимает ввод с клавиатуры и обрабатывает каждую строку ввода как событие. Когда строка передается из System.in
, затем вызывается метод notifyObservers()
, чтобы уведомить всех наблюдателей о возникновении события в форме вызова их методов обновления.
импортировать java.util.ArrayList ; импортировать java.util.List ; импортировать java.util.Сканер ; интерфейс Observer { void update ( String event ); } Класс EventSource { Список < Наблюдатель > наблюдатели = новый ArrayList <> (); public void notifyObservers ( String event ) { observers . forEach ( наблюдатель -> наблюдатель . обновление ( событие )); } Public void addObserver ( Наблюдатель -наблюдатель ) { Observers . добавить ( наблюдатель ); } Public void scanSystemIn () { Сканер-сканер = новый сканер ( System . in ) ; while ( Scanner . hasNextLine ()) { Строковая строка = Scanner . следующаястрока (); notifyObservers ( строка ); } } } общественный класс ObserverDemo { public static void main ( String [] args ) { System . вне . println ( "Введите текст:" ); EventSource eventSource = новый EventSource (); источник событий . addObserver ( event -> System.out.println ( "Получен ответ : " + событие ) ) ; источник событий . сканированиеСистемыВ (); } }
Это реализация C++11.
#include <функционал> #include <iostream> #include <список> класс Тема ; // Форвардное объявление для использования в Observer класс Observer { public : явный наблюдатель ( Subject & subj ); виртуальный ~ Наблюдатель (); Наблюдатель ( const Observer & ) = удалить ; // правило трех Observer & оператор = ( const Observer & ) = delete ; обновление виртуальной пустоты ( Subject & s ) const = 0 ; Private : // Ссылка на объект субъекта, который нужно отсоединить в деструкторе. Тема & субъект ; }; // Субъект является базовым классом для генерации событий class subject { public : using RefObserver = std :: reference_wrapper < const Observer > ; // Уведомляем всех прикрепленных наблюдателей void notify () { for ( const auto & x : Observers ) { x . получать (). обновить ( * это ); } } // Добавляем наблюдателя void Attach ( const Observer & Observer ) { Observers . push_front ( наблюдатель ); } // Удаление наблюдателя void detach ( Observer & Observer ) { Observers . Remove_if ( [ & наблюдатель ] ( const RefObserver & obj ) { return & obj . get () ==& наблюдатель ; }); } Private : std :: list <RefObserver> наблюдатели ; }; Наблюдатель :: Наблюдатель ( Субъект и subj ) : субъект ( subj ) { subject . прикрепить ( * это ); } Наблюдатель ::~ Наблюдатель () { тема . отсоединить ( * это ); } // Пример использования class ConcreteObserver : public Observer { public : ConcreteObserver ( Тема & subj ) : Observer ( subj ) {} // Получение уведомления void update ( Тема & ) const override { std :: cout << "Получил уведомление" << std :: endl ; } }; int main () { Тема cs ; ConcreteObserver co1 ( CS ); ConcreteObserver co2 ( CS ); сс . поставить в известность (); }
Вывод программы такой
Получил уведомление Получил уведомление _ _
класс EventSource { частные наблюдатели = [] Private notifyObservers ( String event ) { observers . каждый { оно ( событие ) } } void addObserver ( наблюдатель ) { наблюдатели += наблюдатель } void scanSystemIn () { var Scanner = новый сканер ( System . in ) while ( scanner ) { var line = Scanner . nextLine () notifyObservers ( строка ) } } } println 'Введите текст:' var eventSource = новый EventSource () источник событий . addObserver { событие -> println «Получен ответ: $event» } источник событий . сканСистемВн ()
импортировать java.util.Scanner typealias Observer = ( событие : String ) -> Unit ; класс EventSource { частные var наблюдатели = mutableListOf < Observer > () частное развлечение notifyObservers ( событие : String ) { observers . forEach { оно ( событие ) } } весело addObserver ( наблюдатель : наблюдатель ) { наблюдатели += наблюдатель } fun scanSystemIn () { val Scanner = Scanner ( System . `in` ) while ( Scanner . HasNext ()) { val Line = Scanner . nextLine () notifyObservers ( строка ) } } }
fun main ( arg : List < String > ) { println ( «Введите текст:» ) val eventSource = EventSource () источник событий . addObserver { event -> println ( «Получен ответ: $ event » ) } источник событий . сканСистемИн () }
использует систему . Дженерики . Коллекции , Система . СисУтилс ; тип IObserver = интерфейс [ '{0C8F4C5D-1898-4F24-91DA-63F1DD66A692}' ] процедура Update ( const AValue : string ) ; конец ; тип TObserverManager = частный класс FObservers : TList <IObserver> ; общедоступный конструктор Create ; перегрузка ; деструктор Уничтожить ; переопределить ; процедура NotifyObservers ( const AValue : string ) ; процедура AddObserver ( const AObserver : IObserver ) ; процедура UnregisterObsrver ( const AObserver : IObserver ) ; конец ; тип TListener = класс ( TInterfacedObject , IObserver ) частное FName : строка ; общедоступный конструктор Create ( const AName : string ) ; вновь представить ; процедура Update ( const AValue : string ) ; конец ; процедура TObserverManager . AddObserver ( const AObserver : IObserver ) ; начать , если не Fobservers . Содержит ( AObserver ), затем FOBservers . Добавить ( AObserver ) ; конец ; начать FreeAndNil ( FObservers ) ; унаследовано ; конец ; процедура TObserverManager . NotifyObservers ( const AValue : строка ) ; вар я : целое число ; начать с i := 0 до Fobservers . Подсчитайте - 1 сделайте Fobservers [ i ] . Обновление ( AValue ) ; конец ; процедура TObserverManager . UnregisterObsrver ( const AObserver : IObserver ) ; начать , если FOBservers . Содержит ( AObserver ), затем FOBservers . Удалить ( AObserver ) ; конец ; конструктор TListener . Create ( const AName : string ) ; начать унаследованное Create ; FName := AName ; конец ; процедура TListener . Обновление ( const AValue : строка ) ; начать WriteLn ( FName + 'прослушиватель получил уведомление: ' + AValue ) ; конец ; процедура TMyForm . ObserverExampleButtonClick ( Отправитель : TObject ) ; вар LDoorNotify : TObserverManager ; LListenerHusband : IObserver ; LListenerWife : IObserver ; начать LDoorNotify := TObserverManager . Создавать ; попробуйте LListenerHusband := TListener . Создать ( «Муж» ) ; LDoorNotify . AddObserver ( LListenerHusband ) ; LListenerWife := TListener . Создать ( «Жена» ) ; LDoorNotify . AddObserver ( LListenerWife ) ; LDoorNotify . NotifyObservers ( 'Кто-то стучится в дверь' ) ; наконец FreeAndNil ( LDoorNotify ) ; конец ; конец ;
Выход
Слушатель мужа получил уведомление: кто-то стучится в дверьЖена-слушатель получила уведомление: кто-то стучится в дверь
Аналогичный пример в Python :
класс Observable : def __init__ ( self ): self . _observers = [] защита Register_observer ( я , наблюдатель ): сам . _наблюдатели . добавить ( наблюдатель ) def notify_observers ( self , * args , ** kwargs ): для obs в self . _observers : наблюдатели . уведомить ( self , * args , ** kwargs )класс Observer : def __init__ ( self , наблюдаемый ): наблюдаемый . Register_observer ( я ) def notify ( self , observable , * args , ** kwargs ): print ( «Получил» , args , kwargs , «От» , наблюдаемый )субъект = Наблюдаемый () наблюдатель = Наблюдатель ( субъект ) субъект . notify_observers ( «тест» , kw = «python» )# печатает: Got ('test',) {'kw': 'python'} Из объекта <__main__.Observable по адресу 0x0000019757826FD0>
класс Payload { внутренняя строка Message { get ; набор ; } } класс Тема : IObservable < Полезная нагрузка > { частное только для чтения ICollection < IObserver < Полезная нагрузка >> _observers = новый список < IObserver < Полезная нагрузка >> (); IDisposable IObservable < Полезная нагрузка > . Подписаться ( IObserver <Payload> Observer ) { if ( ! _observers.Содержит ( наблюдатель ) ) { _observers . _ _ _ Добавить ( наблюдатель ); } вернуть нового Отписавшегося ( наблюдатель , _observers ); } внутренняя пустота SendMessage ( строковое сообщение ) { foreach ( var Observer в _observers ) { observer . OnNext ( новая полезная нагрузка { Сообщение = сообщение }); } } } внутренний класс Unsubscriber : IDisposable { частный IObserver только для чтения < Полезная нагрузка > _observer ; частный только для чтения ICollection < IObserver < Полезная нагрузка >> _observers ; внутренний отписчик ( IObserver <Payload> Observer , ICollection < IObserver < Payload >> наблюдатели ) { _observer = Observer ; _observers = наблюдатели ; } void IDisposable . Dispose () { if ( _observer != null && _observers . Содержит ( _observer )) { _observers . Удалить ( _observer ); } } } внутренний класс Observer : IObserver <Payload> { внутренняя строка Message { get ; _ _ набор ; } общественный недействительный OnCompleted () { } public void OnError ( ошибка исключения ) { } public void OnNext ( значение полезной нагрузки ) { Сообщение = значение . Сообщение ; } внутренний IDisposable Register ( IObservable <Payload> subject ) { return subject . _ _ Подписаться ( это ); } }
В JavaScript есть устаревшая Object.observe
функция, которая представляла собой более точную реализацию шаблона наблюдателя. [7] Это вызовет события при изменении наблюдаемого объекта. Без устаревшей Object.observe
функции шаблон можно реализовать с помощью более явного кода: [8]
пусть Тема = { _state : 0 , _observers : [], добавьте : функция ( наблюдатель ) { this . _наблюдатели . нажать ( наблюдатель ); }, getState : function () { return this . _состояние ; }, setState : функция ( значение ) { this . _state = значение ; for ( пусть я знак равно 0 ; я < this . _observers . length ; я ++ ) { this . _наблюдатели [ я ]. сигнал ( это ); } } }; let Observer = { сигнал : функция ( субъект ) { let currentValue = субъект . ПолучитьСостояние (); консоль . журнал ( текущее значение ); } } Предмет . добавить ( Наблюдатель ); Предмет . УстановитьСостояние ( 10 ); //Вывод в console.log — 10