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

редактировать

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

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

Содержание

  • 1 Обзор
    • 1.1 Какие проблемы может решить шаблон проектирования Observer?
    • 1.2 Какое решение описывает шаблон проектирования Observer?
  • 2 Сильная или слабая ссылка
  • 3 Связь и типичные реализации pub-sub
    • 3.1 Несвязанное
  • 4 Структура
    • 4.1 Класс UML и диаграмма последовательности
    • 4.2 Диаграмма классов UML
  • 5 Пример
    • 5.1 Java
    • 5.2 Groovy
    • 5.3 Kotlin
    • 5.4 Delphi
    • 5.5 Python
    • 5.6 C #
    • 5.7 JavaScript
  • 6 См. Также
  • 7 Ссылки
  • 8 Внешние ссылки

Обзор

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

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

Шаблон «Наблюдатель» решает следующие проблемы:

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

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

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

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

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

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

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

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

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

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

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

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

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

Uncoupled

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

Структура

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

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

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

На диаграмме последовательности UML показаны взаимодействия во время выполнения: объекты 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

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

Groovy

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

Kotlin

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

Delphi

использует System.Generics.Collections, System.SysUtils; тип IObserver = interface ['{0C8F4C5D-1898-4F24-91DA-63F1DD66A692}'] Обновление процедуры (const AValue: string); конец; тип TEdijsObserverManager = класс строгих частных FObservers: TList ; публичный конструктор 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.Contains (AObserver), то FObservers.Add (AObserver); конец; begin FreeAndNil (FObservers); унаследованный; конец; процедура TEdijsObserverManager.NotifyObservers (const AValue: string); var i: целое число; begin for i: = 0 to FObservers.Count - 1 do FObservers [i].Update (AValue); конец; процедура TEdijsObserverManager.UnregisterObsrver (const AObserver: IObserver); начать, если FObservers.Contains (AObserver), затем FObservers.Remove (AObserver); конец; конструктор TListener.Create (const AName: string); begin унаследовано Create; FName: = AName; конец; процедура TListener.Update (const AValue: string); begin WriteLn (FName + 'слушатель получил уведомление:' + AValue); конец; процедура TEdijsForm.ObserverExampleButtonClick (Sender: TObject); var _DoorNotify: TEdijsObserverManager; _ListenerHusband: IObserver; _ListenerWife: IObserver; begin _DoorNotify: = TEdijsObserverManager.Create; попробуйте _ListenerHusband: = TListener.Create ('Муж'); _DoorNotify.AddObserver (_ListenerHusband); _ListenerWife: = TListener.Create ('Жена'); _DoorNotify.AddObserver (_ListenerWife); _DoorNotify.NotifyObservers («Кто-то стучится в дверь»); наконец FreeAndNil (_DoorNotify); конец; конец;

Вывод

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

Python

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

class Observable: def __init __ (self): self._observers = def register_observer (self, Observer): self._observers.append (Observer) def notify_observers (self, * args, ** kwargs): для наблюдателя в self._observers: Observer.notify (self, * args, ** kwargs) class Observer: def __init __ (self, observable): observable.register_observer (self) def notify (self, observable, * args, ** kwargs): print ('Got', args, kwargs, 'From', observable) subject = Observable () Observer = Observer (subject) subject.notify_observers ('test')

C #

public class Payload {public string Message { получить; задавать; }}
открытый класс Subject: IObservable {public IList >Observers {get; задавать; } public Subject () {Observers = новый список >(); } общедоступная IDisposable Subscribe (IObserver наблюдатель) {если (! Observers.Contains (наблюдатель)) {Observers.Add (наблюдатель); } return new Unsubscriber (Наблюдатели, наблюдатель); } public void SendMessage (строковое сообщение) {foreach (наблюдатель var в наблюдателях) {наблюдатель.OnNext (новая полезная нагрузка {сообщение = сообщение}); }}}
открытый класс Unsubscriber: IDisposable {private IObserver наблюдатель; частные наблюдатели IList >; общедоступный отменивший подписку (наблюдатели IList >, наблюдатели IObserver ) {this.observers = Observers; this.observer = наблюдатель; } public void Dispose () {если (наблюдатель! = ноль Observers.Contains (наблюдатель)) {наблюдатели.Remove (наблюдатель); }}}
общедоступный класс Observer: IObserver {общедоступная строка Message {get; задавать; } public void OnCompleted () {} public void OnError (исключительная ошибка) {} public void OnNext (значение полезной нагрузки) {Message = value.Message; } общедоступный IDisposable Register (Subject subject) {return subject.Subscribe (это); }}

JavaScript

Существуют библиотеки и фреймворки для JavaScript, использующие шаблон наблюдателя. Одна из таких библиотек - RxJS, показанная ниже.

// импортируем оператор fromEvent import {fromEvent} from 'rxjs'; // получение ссылки на кнопку const button = document.getElementById ('myButton'); // создаем наблюдаемое нажатие кнопок const myObservable = fromEvent (button, 'click'); // пока давайте просто регистрируем событие при каждом нажатии const subscription = myObservable.subscribe (event =>console.log (event));

См. Также

Ссылки

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

Последняя правка сделана 2021-06-01 07:31:01
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте