stringtranslate.com

Модель наблюдателя

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

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

Обзор

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

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

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

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

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

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

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

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

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

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

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

Несцепленный

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

Структура

Диаграмма классов и последовательностей 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.Scanner ;   interface  Observer { void update ( String event ); } class EventSource { List < Observer > observers = new ArrayList < > (); public void notifyObservers ( String event ) { observers.forEach ( observer - > observer.update ( event ) ) ; } 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 ObserverDemo { public static void main ( String [] args ) { System.out.println ( " Введите текст: " ) ; EventSource eventSource = new EventSource ( ) ; eventSource.addObserver ( event - > System.out.println ( " Получен ответ: " + event ) ) ;                      eventSource.scanSystemIn ( ) ; } } 

С++

Это реализация C++11.

#include <функциональный> #include <поток_вывода> #include <список>   class Subject ; //Предварительное объявление для использования в Observer  class Observer { public : explicit Observer ( Subject & subj ); virtual ~ Observer (); Observer ( const Observer & ) = delete ; // правило трех Observer & оператор = ( const Observer & ) = delete ;                  virtual void update ( Subject & s ) const = 0 ; private : // Ссылка на объект Subject для отсоединения в деструкторе Subject & subject ; };          // Subject — базовый класс для генерации событий class Subject { public : using RefObserver = std :: reference_wrapper < const Observer > ; // Уведомить всех прикрепленных наблюдателей void notify () { for ( const auto & x : observers ) { x . get (). update ( * this ); } } // Добавить наблюдателя void attach ( const Observer & observer ) { observers . push_front ( observer ); } // Удалить наблюдателя void detach ( Observer & observer ) { observers . remove_if ( [ & observer ] ( const RefObserver & obj ) { return & obj . get () == & observer ; }); } private : std :: list < RefObserver > observers ; };                                                    Наблюдатель :: Наблюдатель ( Тема и подпункт ) : тема ( подпункт ) { тема . прикрепить ( * это ); }    Наблюдатель ::~ Наблюдатель () { субъект . отсоединить ( * это ); } // Пример использования class ConcreteObserver : public Observer { public : ConcreteObserver ( Subject & subj ) : Observer ( subj ) {} // Получить уведомление void update ( Subject & ) const override { std :: cout << "Получил уведомление" << std :: endl ; } };                     int main ( ) { Subject cs ; ConcreteObserver co1 ( cs ); ConcreteObserver co2 ( cs ); cs.notify ( ); }         

Вывод программы такой:

Получил уведомление Получил уведомление    

Круто

класс EventSource { частные наблюдатели = []       private notifyObservers ( событие строки ) { observers.each { it ( событие ) } }         void addObserver ( наблюдатель ) { наблюдатели += наблюдатель }       void scanSystemIn ( ) { var scanner = new Scanner ( System.in ) while ( scanner ) { var line = scanner.nextLine ( ) notifyObservers ( line ) } } }                 println 'Введите текст: ' var eventSource = new EventSource ()     eventSource.addObserver { event -> println " Получен ответ: $event " }     eventSource.scanSystemIn ( )

Котлин

импорт java.util.Scanner typealias Наблюдатель = ( событие : Строка ) -> Единица ;      класс EventSource { private var observers = mutableListOf < Observer > ()        частная забава notifyObservers ( событие : String ) { observers . forEach { it ( событие ) } }          fun addObserver ( наблюдатель : Наблюдатель ) { наблюдатели += наблюдатель }        fun scanSystemIn ( ) { val scanner = Scanner ( System.`in` ) while ( scanner.hasNext ( ) ) { val line = scanner.nextLine ( ) notifyObservers ( line ) } } }                
fun main ( arg : List < String > ) { println ( "Введите текст: " ) val eventSource = EventSource ()         eventSource.addObserver { event -> println ( " Получен ответ: $ event " ) }      eventSource.scanSystemIn ( ) }

Дельфи

использует System.Generics.Collections , System.SysUtils ;  тип IObserver = интерфейс [ '{0C8F4C5D-1898-4F24-91DA-63F1DD66A692}' ] процедура Обновление ( const AValue : string ) ; конец ;         тип TObserverManager = класс private FObservers : TList < IObserver >; открытый конструктор Создать ; перегрузить ; деструктор Уничтожить ; переопределить ; процедура NotifyObservers ( const AValue : string ) ; процедура AddObserver ( const AObserver : IObserver ) ; процедура UnregisterObsrver ( const AObserver : IObserver ) ; конец ;                          тип TListener = class ( TInterfacedObject , IObserver ) private FName : string ; public конструктор Create ( const AName : string ) ; повторно ввести ; процедура Update ( const AValue : string ) ; end ;                  procedure TObserverManager.AddObserver ( const AObserver : IObserver ) ; begin if not FObservers.Contains ( AObserver ) then FObservers.Add ( AObserver ) ; end ;        begin FreeAndNil ( FObservers ) ; наследуется ; конец ;  procedure TObserverManager.NotifyObservers ( const AValue : string ) ; var i : Integer ; begin for i : = 0 to FObservers.Count - 1 do FObservers [ i ] .Update ( AValue ) ; end ;               procedure TObserverManager.UnregisterObsrver ( const AObserver : IObserver ) ; begin if FObservers.Contains ( AObserver ) then FObservers.Remove ( AObserver ) ; end ;       конструктор TListener.Create ( const AName : string ) ; begin inherited Create ; FName : = AName ; end ;        procedure TListener.Update ( const AValue : string ) ; begin WriteLn ( FName + ' слушатель получил уведомление: ' + AValue ) ; end ;        procedure TMyForm.ObserverExampleButtonClick ( Sender : TObject ) ; var LDoorNotify : TObserverManager ; LListenerHusband : IObserver ; LListenerWife : IObserver ; begin LDoorNotify : = TObserverManager.Create ; try LListenerHusband : = TListener.Create ( ' Муж ' ) ; LDoorNotify.AddObserver ( LListenerHusband ) ; LListenerWife : = TListener.Create ( ' Жена ' ) ; LDoorNotify.AddObserver ( LListenerWife ) ; LDoorNotify.NotifyObservers ( ' Кто - то стучится в дверь ' ) ; наконец FreeAndNil ( LDoorNotify ) ; конец ; конец ;                        

Выход

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

Питон

Похожий пример на Python :

класс  Наблюдаемый :  def  __init__ ( self ):  self . _observers  =  [] def  register_observer ( self ,  observer )  - >  None :  self._observers.append ( observer ) def  notify_observers ( self ,  * args ,  ** kwargs )  - >  None :  для  наблюдателя  в  self._observers : observer.notify ( self , * args , ** kwargs )    класс  Observer :  def  __init__ ( self ,  observable )  : observable.register_observer ( self ) def  notify ( self ,  observable ,  * args ,  ** kwargs )  ->  None :  print ( "Получил" ,  args ,  kwargs ,  "От" ,  observable )субъект  =  Наблюдаемый () наблюдатель  =  Наблюдатель ( субъект ) субъект . notify_observers ( "тест" ,  kw = "python" )# выводит: Got ('test',) {'kw': 'python'} Из <__main__.Observable object at 0x0000019757826FD0>

С#

класс Полезная нагрузка { внутренняя строка Сообщение { получить ; установить ; } }        класс Тема : IObservable < Полезная нагрузка > { private readonly ICollection < IObserver < Полезная нагрузка >> _observers = new List < IObserver < Полезная нагрузка >> ();           IDisposable IObservable < полезная нагрузка > .Подписаться ( IObserver < полезная нагрузка > наблюдатель ) { if ( ! _ observers.Содержит ( наблюдатель ) ) { _observers.Добавить ( наблюдатель ) ; }          вернуть нового Отписавшегося ( наблюдателя , _наблюдатели ); }     внутренний void SendMessage ( string message ) { foreach ( var observer in _observers ) { observer . OnNext ( new Payload { Message = message }); } } }                   внутренний класс Отказ от подписки : IDisposable { private readonly IObserver < Полезная нагрузка > _observer ; private readonly ICollection < IObserver < Полезная нагрузка >> _observers ;             внутренний Отписчик ( IObserver < Полезная нагрузка > наблюдатель , ICollection < IObserver < Полезная нагрузка >> наблюдатели ) { _observer = наблюдатель ; _observers = наблюдатели ; }              void IDisposable.Dispose ( ) { if ( _observer ! = null && _observers.Contains ( _observer ) ) { _observers.Remove ( _observer ) ; } } }            внутренний класс Observer : IObserver < Полезная нагрузка > { внутренняя строка Сообщение { получить ; установить ; }            public void OnCompleted () { }     public void OnError ( Исключительная ошибка ) { }      public void OnNext ( Значение полезной нагрузки ) { Сообщение = значение . Сообщение ; }         внутренний IDisposable Register ( IObservable < Payload > subject ) { return subject . Subscribe ( this ); } }       

JavaScript

В JavaScript есть устаревшая Object.observeфункция, которая была более точной реализацией шаблона наблюдателя. [7] Это вызвало бы события при изменении наблюдаемого объекта. Без устаревшей Object.observeфункции шаблон может быть реализован с помощью более явного кода: [8]

пусть Subject = { _state : 0 , _observers : [], add : function ( observer ) { this . _observers . push ( observer ); }, getState : function () { return this . _state ; }, setState : function ( value ) { this . _state = value ; for ( let i = 0 ; i < this . _observers . length ; i ++ ) { this . _observers [ i ]. signal ( this ); } } };                                     пусть Наблюдатель = { сигнал : функция ( субъект ) { пусть текущееЗначение = субъект . getState (); консоль . журнал ( текущееЗначение ); } }            Subject.add ( Observer ); Subject.setState ( 10 ); // Вывод в console.log - 10

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

Ссылки

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

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