Jump to content

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Несвязанный

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

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

Структура

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

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

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

В этой 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() затем вызывается для уведомления всех наблюдателей о возникновении события в форме вызова их методов обновления.

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 имеет устаревший 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

См. также

[ редактировать ]
  1. ^ Эрих Гамма; Ричард Хелм; Ральф Джонсон; Джон Влиссидес (1994). Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования . Эддисон Уэсли. стр. 293 и далее . ISBN  0-201-63361-2 .
  2. ^ «Шаблон проектирования наблюдателя» . www.geeksforgeeks.org .
  3. ^ Сравнение различных реализаций шаблона наблюдателя Моше Биндлер, 2015 (Github)
  4. ^ Различия между шаблонами паб/подписка и шаблон наблюдателя. Шаблон наблюдателя Ади Османи (онлайн-книги о сафари)
  5. ^ Опыт программирования для Windows Чарльз Петцольд , 10 ноября 1992 г., журнал PC Magazine ( Google Книги )
  6. ^ «Шаблон проектирования Observer — структура и сотрудничество» . w3sDesign.com . Проверено 12 августа 2017 г.
  7. ^ «jQuery — прослушивание изменений переменных в JavaScript» .
  8. ^ «Jquery — прослушивание изменений переменных в JavaScript» .
[ редактировать ]
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: db0f617fc630e85c34bd5970a447fb08__1720628580
URL1:https://arc.ask3.ru/arc/aa/db/08/db0f617fc630e85c34bd5970a447fb08.html
Заголовок, (Title) документа по адресу, URL1:
Observer pattern - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)