Шаблон наблюдателя является шаблоном проектирования программного обеспечения , в котором объект , названный субъект , поддерживает список своих иждивенцев, называемые наблюдателей , и уведомляет их автоматически любые изменения состояния, как правило , путем вызова одного из своих методов .
В основном используется для реализации распределенной обработки событий.системы, в программном обеспечении, управляемом событиями. В этих системах субъекта обычно называют «потоком событий» или «источником потока событий», а наблюдателей - «приемниками событий». Номенклатура потока ссылается на физическую установку, в которой наблюдатели физически разделены и не имеют контроля над событиями, генерируемыми объектом / источником потока. Затем этот шаблон идеально подходит для любого процесса, в котором данные поступают из некоторого ввода, недоступного для ЦП при запуске, а вместо этого прибывают «случайным образом» (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 диаграммы классов , то Subject
класс не обновляет состояние зависимых объектов непосредственно. Вместо этого Subject
ссылается на Observer
interface ( update()
) для обновления состояния, который делает Subject
независимым от того, как обновляется состояние зависимых объектов. Observer1
И Observer2
классы реализуют Observer
интерфейс путем синхронизации их состояние с состоянием субъекта.
В UML - диаграмма последовательности
показывает время выполнения взаимодействий: The Observer1
и Observer2
объекты называют attach(this)
по Subject1
регистрировать себя. Предполагая, что состояние Subject1
меняется, Subject1
вызывает notify()
само себя.notify()
звонки update()
на зарегистрированные Observer1
и Observer2
объектах, которые запрашивают измененные данные ( getState()
) от Subject1
до обновления (синхронизировать) их состояние.
Диаграмма классов UML [ править ]
Пример [ править ]
Хотя классы библиотеки 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
См. Также [ править ]
- Неявный вызов
- Клиент-серверная модель
- Узор наблюдатель часто используется в сущность-компонентной системы шаблона
Ссылки [ править ]
- ↑ Эрих Гамма; Ричард Хелм; Ральф Джонсон; Джон Влиссидес (1994). Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования . Эддисон Уэсли. стр. 293ff . ISBN 0-201-63361-2.
- ^ «Шаблон проектирования Observer - проблема, решение и применимость» . w3sDesign.com . Проверено 12 августа 2017 .
- ^ Сравнение различных реализаций паттернов наблюдателя Моше Биндлер, 2015 (Github)
- ^ Различия между шаблоном pub / sub и наблюдателем The Observer Pattern Ади Османи (книги по Safari в Интернете)
- ↑ The Windows Programming Experience Чарльз Петцольд , 10 ноября 1992 г., PC Magazine ( Google Книги )
- ^ «Шаблон проектирования Observer - Структура и сотрудничество» . w3sDesign.com . Проверено 12 августа 2017 .
- ^ https://stackoverflow.com/a/50862441/887092
- ^ https://stackoverflow.com/a/37403125/887092
Внешние ссылки [ править ]
- Реализации наблюдателя на разных языках в Викиучебнике