stringtranslate.com

Шаблон наблюдателя

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

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

Обзор

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

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

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

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

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

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

Связывание и типичные реализации публикации-подписки

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

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

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

Связанные шаблоны включают публикацию-подписку, медиатор и синглтон .

Несвязанный

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

Состав

Класс UML и диаграмма последовательности

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

В этой диаграмме классов UML класс не обновляет состояние зависимых объектов напрямую. Вместо этого относится к интерфейсу ( ) для обновления состояния, что делает независящим от того, как обновляется состояние зависимых объектов. Классы и реализуют интерфейс, синхронизируя свое состояние с состоянием субъекта.SubjectSubjectObserverupdate()SubjectObserver1Observer2Observer

Диаграмма последовательности UML показывает взаимодействие во время выполнения: объекты и вызывают регистрацию себя. Предполагая, что состояние меняется, взывает к себе. обращается к зарегистрированным объектам и объектам, которые запрашивают измененные данные ( ) для обновления (синхронизации) своего состояния.Observer1Observer2attach(this)Subject1Subject1Subject1notify()notify()update()Observer1Observer2getState()Subject1

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

Диаграмма классов UML шаблона Observer

Пример

Хотя библиотечные классы 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

В 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

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

Рекомендации

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

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