Из Википедии, бесплатной энциклопедии
Перейти к навигации Перейти к поиску

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

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

Обзор [ править ]

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

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

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

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

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

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

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

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

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

Сильная и слабая ссылка [ править ]

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

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

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

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

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

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

Несвязанный [ править ]

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

Структура [ править ]

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

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

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

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

Диаграмма классов UML [ править ]

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

Пример [ править ]

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

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

Java [ править ]

import  java.util.List ; import  java.util.ArrayList ; import  java.util.Scanner ;class  EventSource  {  общедоступный  интерфейс  Observer  {  недействительное  обновление ( событие String  ); }   закрытый  финальный  список < Наблюдатель >  наблюдатели  =  новый  ArrayList <> ();  private  void  notifyObservers ( событие String  ) { наблюдатели . forEach ( наблюдатель -> наблюдатель . обновление ( событие )); }       public  void  addObserver ( наблюдатель-  наблюдатель )  {  наблюдатели . добавить ( наблюдатель );  }  public  void  scanSystemIn ()  {  Сканер  сканер  =  новый  Сканер ( System . in );  while  ( сканер . hasNextLine ())  {  Строка  строки  =  сканер . nextLine ();  notifyObservers ( строка );  }  } }
открытый  класс  ObserverDemo  {  public  static  void  main ( String []  args )  {  System . из . println ( "Введите текст:" );  EventSource  EventSource  =  новый  EventSource ();  eventSource . addObserver ( событие  ->  {  System . out . println ( "Полученный ответ:"  +  событие );  }); eventSource . scanSystemIn ();  } }

Groovy [ править ]

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

Котлин [ править ]

import  java.util.Scannertypealias  Observer  =  ( событие :  String )  ->  Unit ;class  EventSource  {  частные  наблюдатели var  = mutableListOf < Observer > ()   частное  развлечение  notifyObservers ( событие :  строка )  {  наблюдатели . forEach  {  это ( событие )  }  } весело  addObserver ( наблюдатель :  Наблюдатель )  {  наблюдатели  + =  наблюдатель  } fun  scanSystemIn ()  {  val  scanner  =  Scanner ( System . `in` )  while  ( scanner . hasNext ())  {  val  line  =  scanner . nextLine ()  notifyObservers ( строка )  }  } }
весело  главный ( Arg :  Список < строка > )  {  Println ( "Введите текст:" )  Вал  EventSource  =  EventSource () eventSource . addObserver  {  событие  ->  println ( "Получен ответ: $ event " )  } eventSource . scanSystemIn () }

Delphi [ править ]

использует  System . Дженерики . Коллекции  ,  Система . SysUtils  ;тип  IObserver  =  interface  [ '{0C8F4C5D-1898-4F24-91DA-63F1DD66A692}' ]  Обновление процедуры  ( const AValue : string ) ; конец ;   тип  TEdijsObserverManager  =  класс  строгих  частных  FObservers :  TList < IObserver >;  публичный  конструктор  Create ;  перегрузка ;  деструктор  Destroy ;  переопределить ;  процедура  NotifyObservers ( const  AValue :  string ) ;  процедура  AddObserver ( const  AObserver :  IObserver ) ;  процедура  UnregisterObsrver ( const AObserver :  IObserver ) ;  конец ;тип  TListener  =  class ( TInterfacedObject ,  IObserver )  strict  private  FName :  string ;  открытый  конструктор  Create ( const  AName :  string ) ;  повторно ввести ;  Обновление процедуры  ( const AValue : string ) ; конец ;   процедура  TEdijsObserverManager . AddObserver ( const  AObserver :  IObserver ) ; начни,  если  не  FObservers . Содержит ( AObserver ),  затем  FObservers . Добавить ( AObserver ) ; конец ;begin  FreeAndNil ( FObservers ) ;  унаследованный ; конец ;процедура  TEdijsObserverManager . NotifyObservers ( const  AValue :  строка ) ; var  i :  целое число ; begin  for  i  : =  0  в  FObservers . Подсчет  -  1  do  FObservers [ i ] . Обновить ( AValue ) ; конец ;процедура  TEdijsObserverManager . UnregisterObsrver ( const  AObserver :  IObserver ) ; начать,  если  FObservers . Содержит ( AObserver ),  затем  FObservers . Удалить ( AObserver ) ; конец ;конструктор  TListener . Создать ( const  AName :  строка ) ; begin  унаследовано  Create ;  FName  : =  AName ; конец ;процедура  TListener . Обновление ( const  AValue :  строка ) ; begin  WriteLn ( FName  +  'слушатель получил уведомление:'  +  AValue ) ; конец ;процедура  TEdijsForm . ObserverExampleButtonClick ( Отправитель :  TObject ) ; var  _DoorNotify :  TEdijsObserverManager ;  _ListenerHusband :  IObserver ;  _ListenerWife :  IObserver ; begin  _DoorNotify  : =  TEdijsObserverManager . Создать ;  попробуйте  _ListenerHusband  : =  TListener . Создать ( 'Муж' ) ;  _DoorNotify . AddObserver (_ListenerHusband ) ; _ListenerWife  : =  TListener . Создать ( «Жена» ) ;  _DoorNotify . AddObserver ( _ListenerWife ) ; _DoorNotify . NotifyObservers ( «Кто-то стучится в дверь» ) ;  наконец  FreeAndNil ( _DoorNotify ) ;  конец ; конец ;

Выход

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

Python [ править ]

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

class  Observable :  def  __init__ ( self )  ->  None :  self . _observers  =  []  def  register_observer ( сам ,  наблюдатель )  ->  Нет :  сам . _observers . добавить ( наблюдатель )  def  notify_observers ( self ,  * args ,  ** kwargs )  ->  None :  для  наблюдателя  в  себе . _observers :  наблюдатель . уведомить ( self ,  * args ,  ** kwargs )class  Observer :  def  __init__ ( self ,  observable )  ->  None :  observable . register_observer ( сам )  def  notify ( self ,  observable ,  * args ,  ** kwargs )  ->  None :  print ( "Got" ,  args ,  kwargs ,  "From" ,  observable )subject  =  Observable () наблюдатель  =  Наблюдатель ( субъект ) субъект . notify_observers ( «тест» )

C # [ править ]

 открытый  класс  Payload  {  общедоступная  строка  Сообщение  {  получить ;  набор ;  }  }
 открытый  класс  Subject  :  IObservable < Payload >  {  public  IList < IObserver < Payload >>  Observers  {  get ;  набор ;  } public  Subject ()  {  Наблюдатели  =  новый  список < IObserver < Payload >> ();  } public  IDisposable  Subscribe ( IObserver < Payload >  наблюдатель )  {  if  (! Observers . Содержит ( наблюдатель ))  {  Observers . Добавить ( наблюдатель );  }  return  new  Unsubscriber ( Наблюдатели ,  наблюдатель );  } public  void  SendMessage ( строковое  сообщение )  {  foreach  ( наблюдатель var  в наблюдателях ) { наблюдатель . OnNext ( новая полезная нагрузка { сообщение = сообщение }); } } }             
 открытый  класс  Unsubscriber  :  IDisposable  {  private  IObserver < Payload >  наблюдатель ;  частные  наблюдатели IList < IObserver < Payload >>  ; общественного Unsubscriber ( IList < IObserver < Payload >> наблюдатели , IObserver < Payload > наблюдатель ) { это . наблюдатели = наблюдатели ; это .          наблюдатель  =  наблюдатель ;  } public  void  Dispose ()  {  если  ( наблюдатель  ! =  ноль  &&  наблюдатели . Содержит ( наблюдатель ))  {  наблюдатели . Удалить ( наблюдатель );  }  }  }
 общедоступный  класс  Наблюдатель  :  IObserver < Payload >  {  общедоступная  строка  Сообщение  {  получить ;  набор ;  } public  void  OnCompleted ()  {  } public  void  OnError ( ошибка исключения  ) { }   public  void  OnNext ( значение полезной нагрузки  ) { сообщение = значение . Сообщение ; }      public  IDisposable  Register ( Subject  subject )  {  return  subject . Подписаться ( это );  }  }

JavaScript [ править ]

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

var  Subject  =  {  _state :  0 ,  _observers :  [],  добавить :  функция ( наблюдатель )  {  это . _observers . толкать ( наблюдатель );  },  getState :  function ()  {  вернуть  это . _state ;  },  setState :  function ( value )  {  this . _state  =  значение ;  за ( var  i  =  0 ;  i  <  this . _observers . length ;  i ++ )  {  this . _observers [ i ]. сигнал ( это );  }  } };var  Observer  =  {  сигнал :  функция ( тема )  {  var  currentValue  =  subject . getState ();  консоль . журнал ( currentValue );  } }Тема . добавить ( Наблюдатель ); Тема . setState ( 10 ); // Вывод в console.log - 10

См. Также [ править ]

  • Неявный вызов
  • Клиент-серверная модель
  • Узор наблюдатель часто используется в сущность-компонентной системы шаблона

Ссылки [ править ]

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

Внешние ссылки [ править ]

  • Реализации наблюдателя на разных языках в Викиучебнике