Шаблон наблюдателя
В проектировании и разработке программного обеспечения шаблон наблюдателя — это шаблон проектирования программного обеспечения , в котором объект , называемый субъектом , поддерживает список своих зависимых объектов, называемых наблюдателями , и автоматически уведомляет их о любых изменениях состояния , обычно путем вызова одного из их методов .
Он часто используется для реализации распределенных обработки событий систем в программном обеспечении, управляемом событиями . В таких системах субъект обычно называют «потоком событий» или «источником потока событий», а наблюдателей называют «приемниками событий». Номенклатура потока намекает на физическую установку, в которой наблюдатели физически разделены и не имеют контроля над событиями, исходящими от субъекта/источника потока. Таким образом, этот шаблон подходит для любого процесса, при котором данные поступают с какого-либо входа, который недоступен для ЦП при запуске , но вместо этого может поступать в произвольное или неопределенное время ( запросы HTTP , GPIO данные , пользовательский ввод от периферийных устройств и распределенных баз данных и т. д.). .
Обзор
[ редактировать ]Шаблон проектирования наблюдателя — это поведенческий шаблон, входящий в число 23 известных шаблонов проектирования «Банды четырех» , который решает повторяющиеся проблемы проектирования с целью разработки гибкого и многократно используемого объектно-ориентированного программного обеспечения, создавая объекты, которые легче реализовать, изменить и протестировать. и повторное использование. [1]
Шаблон наблюдателя решает следующие проблемы: [2]
- Зависимость «один ко многим» между объектами должна быть определена без создания тесной связи между объектами.
- Когда один объект меняет состояние, неограниченное количество зависимых объектов должно обновляться автоматически.
- Объект может уведомлять несколько других объектов.
Определение зависимости «один ко многим» между объектами путем определения одного объекта (субъекта), который напрямую обновляет состояние зависимых объектов, является негибким, поскольку оно связывает субъект с конкретными зависимыми объектами. Однако это может быть применимо с точки зрения производительности или если реализация объекта тесно связана (например, низкоуровневые структуры ядра, которые выполняются тысячи раз в секунду). В некоторых сценариях сложно реализовать тесно связанные объекты, и их нелегко использовать повторно, поскольку они ссылаются на множество объектов с разными интерфейсами и знают о них. В других сценариях тесно связанные объекты могут быть лучшим вариантом, поскольку компилятор способен обнаруживать ошибки во время компиляции и оптимизировать код на уровне инструкций ЦП.
- Определять
Subject
иObserver
объекты. - Когда субъект меняет состояние, все зарегистрированные наблюдатели уведомляются и обновляются автоматически (и, возможно, асинхронно).
Единственная ответственность субъекта — вести список наблюдателей и уведомлять их об изменениях состояния, звоня им. update()
операция. В обязанности наблюдателей входит регистрация и отмена регистрации у субъекта (чтобы получать уведомления об изменениях состояния) и обновление своего состояния (чтобы синхронизировать свое состояние с состоянием субъекта) при получении уведомления. Это делает субъект и наблюдателей слабосвязанными. Субъект и наблюдатели не имеют явных знаний друг о друге. Наблюдателей можно добавлять и удалять независимо во время выполнения. Это взаимодействие уведомления и регистрации также известно как публикация-подписка .
Сильная и слабая ссылка
[ редактировать ]Шаблон наблюдателя может вызвать утечку памяти , известную как проблема истекшего прослушивателя , поскольку в базовой реализации он требует как явной регистрации, так и явной отмены регистрации, как в шаблоне удаления , поскольку субъект содержит сильные ссылки на наблюдателей, поддерживая их активность. Этого можно избежать, если субъект имеет слабые связи с наблюдателями.
Связывание и типичные реализации публикации-подписки
[ редактировать ]Обычно шаблон наблюдателя реализуется таким образом, что наблюдаемый субъект является частью объекта, изменения состояния которого наблюдаются (и сообщаются наблюдателям). Этот тип реализации считается тесно связанным , вынуждающим как наблюдателей, так и субъекта знать друг о друге и иметь доступ к их внутренним частям, создавая возможные проблемы масштабируемости , скорости, восстановления и обслуживания сообщений (также называемых потерей событий или уведомлений). , отсутствие гибкости в условном рассредоточении и возможные препятствия для желаемых мер безопасности. В некоторых ( без опроса ) реализациях шаблона публикации-подписки эта проблема решается путем создания выделенного сервера очереди сообщений (а иногда и дополнительного объекта обработчика сообщений) в качестве дополнительного этапа между наблюдателем и наблюдаемым объектом, тем самым отделяя компоненты. В этих случаях к серверу очереди сообщений обращаются наблюдатели с шаблоном наблюдателя, подписывающиеся на определенные сообщения и знающие (или не знающие в некоторых случаях) только об ожидаемом сообщении, ничего не зная при этом о самом отправителе сообщения; отправитель также может ничего не знать о наблюдателях. Другие реализации шаблона публикации-подписки, которые достигают аналогичного эффекта уведомления и связи с заинтересованными сторонами, не используют шаблон наблюдателя. [3] [4]
В ранних реализациях многооконных операционных систем, таких как OS/2 и Windows , термины «шаблон публикации-подписки» и «разработка программного обеспечения, управляемая событиями», использовались как синонимы шаблона наблюдателя. [5]
Шаблон наблюдателя, описанный в книге «Шаблоны проектирования» , представляет собой очень базовую концепцию и не направлен на устранение интереса к изменениям в наблюдаемом субъекте или специальной логике, которую должен выполнять наблюдаемый субъект до или после уведомления наблюдателей. Шаблон также не занимается записью уведомлений об изменениях или гарантией их получения. Эти проблемы обычно решаются в системах очередей сообщений, в которых шаблон наблюдателя играет лишь небольшую роль.
Связанные шаблоны включают публикацию-подписку, медиатор и синглтон .
Несвязанный
[ редактировать ]Шаблон наблюдателя можно использовать при отсутствии публикации-подписки, например, когда статус модели часто обновляется. Частые обновления могут привести к тому, что представление перестанет отвечать на запросы (например, из-за вызова множества вызовов перерисовки ); вместо этого такие наблюдатели должны использовать таймер. Вместо того, чтобы перегружаться сообщением об изменении, наблюдатель будет заставлять представление представлять приблизительное состояние модели через регулярные промежутки времени. Этот режим наблюдения особенно полезен для индикаторов выполнения , в которых ход базовой операции часто меняется.
Структура
[ редактировать ]Класс UML и диаграмма последовательности
[ редактировать ]
В этой 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
[ редактировать ]
Пример
[ редактировать ]Пока занятия в библиотеке java.util.Observer
и java.util.Observable
существуют, они устарели в Java 9, поскольку реализованная модель была весьма ограниченной.
Ниже приведен пример, написанный на Java , который принимает ввод с клавиатуры и обрабатывает каждую строку ввода как событие. Когда строка поставляется из System.in
, метод notifyObservers()
затем вызывается для уведомления всех наблюдателей о возникновении события в форме вызова их методов обновления.
Ява
[ редактировать ]import java.util.ArrayList;
import java.util.List;
import 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("Enter Text: ");
EventSource eventSource = new EventSource();
eventSource.addObserver(event -> System.out.println("Received response: " + event));
eventSource.scanSystemIn();
}
}
С++
[ редактировать ]Это реализация C++11.
#include <functional>
#include <iostream>
#include <list>
class Subject; //Forward declaration for usage in Observer
class Observer
{
public:
explicit Observer(Subject& subj);
virtual ~Observer();
Observer(const Observer&) = delete; // rule of three
Observer& operator=(const Observer&) = delete;
virtual void update( Subject& s) const = 0;
private:
// Reference to a Subject object to detach in the destructor
Subject& subject;
};
// Subject is the base class for event generation
class Subject
{
public:
using RefObserver = std::reference_wrapper<const Observer>;
// Notify all the attached obsevers
void notify()
{
for (const auto& x: observers)
{
x.get().update(*this);
}
}
// Add an observer
void attach(const Observer& observer)
{
observers.push_front(observer);
}
// Remove an observer
void detach(Observer& observer)
{
observers.remove_if( [&observer ](const RefObserver& obj)
{
return &obj.get()==&observer;
});
}
private:
std::list<RefObserver> observers;
};
Observer::Observer(Subject& subj) : subject(subj)
{
subject.attach(*this);
}
Observer::~Observer()
{
subject.detach(*this);
}
// Example of usage
class ConcreteObserver: public Observer
{
public:
ConcreteObserver(Subject& subj) : Observer(subj) {}
// Get notification
void update(Subject&) const override
{
std::cout << "Got a notification" << std::endl;
}
};
int main()
{
Subject cs;
ConcreteObserver co1(cs);
ConcreteObserver co2(cs);
cs.notify();
}
Вывод программы такой
Got a notification
Got a notification
классный
[ редактировать ]class EventSource {
private observers = []
private notifyObservers(String event) {
observers.each { it(event) }
}
void addObserver(observer) {
observers += observer
}
void scanSystemIn() {
var scanner = new Scanner(System.in)
while (scanner) {
var line = scanner.nextLine()
notifyObservers(line)
}
}
}
println 'Enter Text: '
var eventSource = new EventSource()
eventSource.addObserver { event ->
println "Received response: $event"
}
eventSource.scanSystemIn()
Котлин
[ редактировать ]import java.util.Scanner
typealias Observer = (event: String) -> Unit;
class EventSource {
private var observers = mutableListOf<Observer>()
private fun notifyObservers(event: String) {
observers.forEach { it(event) }
}
fun addObserver(observer: Observer) {
observers += observer
}
fun scanSystemIn() {
val scanner = Scanner(System.`in`)
while (scanner.hasNext()) {
val line = scanner.nextLine()
notifyObservers(line)
}
}
}
fun main(arg: List<String>) {
println("Enter Text: ")
val eventSource = EventSource()
eventSource.addObserver { event ->
println("Received response: $event")
}
eventSource.scanSystemIn()
}
Дельфи
[ редактировать ]uses
System.Generics.Collections, System.SysUtils;
type
IObserver = interface
['{0C8F4C5D-1898-4F24-91DA-63F1DD66A692}']
procedure Update(const AValue: string);
end;
type
TObserverManager = class
private
FObservers: TList<IObserver>;
public
constructor Create; overload;
destructor Destroy; override;
procedure NotifyObservers(const AValue: string);
procedure AddObserver(const AObserver: IObserver);
procedure UnregisterObsrver(const AObserver: IObserver);
end;
type
TListener = class(TInterfacedObject, IObserver)
private
FName: string;
public
constructor Create(const AName: string); reintroduce;
procedure 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);
inherited;
end;
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;
constructor TListener.Create(const AName: string);
begin
inherited Create;
FName := AName;
end;
procedure TListener.Update(const AValue: string);
begin
WriteLn(FName + ' listener received notification: ' + AValue);
end;
procedure TMyForm.ObserverExampleButtonClick(Sender: TObject);
var
LDoorNotify: TObserverManager;
LListenerHusband: IObserver;
LListenerWife: IObserver;
begin
LDoorNotify := TObserverManager.Create;
try
LListenerHusband := TListener.Create('Husband');
LDoorNotify.AddObserver(LListenerHusband);
LListenerWife := TListener.Create('Wife');
LDoorNotify.AddObserver(LListenerWife);
LDoorNotify.NotifyObservers('Someone is knocking on the door');
finally
FreeAndNil(LDoorNotify);
end;
end;
Выход
Husband listener received notification: Someone is knocking on the door Wife listener received notification: Someone is knocking on the door
Питон
[ редактировать ]Аналогичный пример в Python :
class Observable:
def __init__(self):
self._observers = []
def register_observer(self, observer) -> None:
self._observers.append(observer)
def notify_observers(self, *args, **kwargs) -> None:
for observer in self._observers:
observer .notify(self, *args, **kwargs)
class Observer:
def __init__(self, observable):
observable.register_observer(self)
def notify(self, observable, *args, **kwargs) -> None:
print("Got", args, kwargs, "From", observable)
subject = Observable()
observer = Observer(subject)
subject.notify_observers("test", kw="python")
# prints: Got ('test',) {'kw': 'python'} From <__main__.Observable object at 0x0000019757826FD0>
С#
[ редактировать ]class Payload
{
internal string Message { get; set; }
}
class Subject : IObservable<Payload>
{
private readonly ICollection<IObserver<Payload>> _observers = new List<IObserver<Payload>>();
IDisposable IObservable<Payload>.Subscribe(IObserver<Payload> observer)
{
if (!_observers.Contains(observer))
{
_observers.Add(observer);
}
return new Unsubscriber(observer, _observers);
}
internal void SendMessage(string message)
{
foreach (var observer in _observers)
{
observer.OnNext(new Payload { Message = message });
}
}
}
internal class Unsubscriber : IDisposable
{
private readonly IObserver<Payload> _observer;
private readonly ICollection<IObserver<Payload>> _observers;
internal Unsubscriber(
IObserver<Payload> observer,
ICollection<IObserver<Payload>> observers)
{
_observer = observer;
_observers = observers;
}
void IDisposable.Dispose()
{
if (_observer != null && _observers.Contains(_observer))
{
_observers.Remove(_observer);
}
}
}
internal class Observer : IObserver<Payload>
{
internal string Message { get; set; }
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
public void OnNext(Payload value)
{
Message = value.Message;
}
internal IDisposable Register(IObservable<Payload> subject)
{
return subject.Subscribe(this);
}
}
JavaScript
[ редактировать ]JavaScript имеет устаревший Object.observe
функция, которая представляла собой более точную реализацию шаблона наблюдателя. [7] Это вызовет события при изменении наблюдаемого объекта. Без устаревшего Object.observe
функция, шаблон может быть реализован с помощью более явного кода: [8]
let 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);
}
}
};
let Observer = {
signal: function(subject) {
let currentValue = subject.getState();
console.log(currentValue);
}
}
Subject.add(Observer);
Subject.setState(10);
//Output in console.log - 10
См. также
[ редактировать ]- Неявный вызов
- Модель клиент-сервер
- Шаблон наблюдателя часто используется в сущность-компонент-система. шаблоне
Ссылки
[ редактировать ]- ^ Эрих Гамма; Ричард Хелм; Ральф Джонсон; Джон Влиссидес (1994). Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования . Эддисон Уэсли. стр. 293 и далее . ISBN 0-201-63361-2 .
- ^ «Шаблон проектирования наблюдателя» . www.geeksforgeeks.org .
- ^ Сравнение различных реализаций шаблона наблюдателя Моше Биндлер, 2015 (Github)
- ^ Различия между шаблонами паб/подписка и шаблон наблюдателя. Шаблон наблюдателя Ади Османи (онлайн-книги о сафари)
- ^ Опыт программирования для Windows Чарльз Петцольд , 10 ноября 1992 г., журнал PC Magazine ( Google Книги )
- ^ «Шаблон проектирования Observer — структура и сотрудничество» . w3sDesign.com . Проверено 12 августа 2017 г.
- ^ «jQuery — прослушивание изменений переменных в JavaScript» .
- ^ «Jquery — прослушивание изменений переменных в JavaScript» .
Внешние ссылки
[ редактировать ]Реализации Observer на разных языках в Wikibooks