Паттерн Observer (наблюдатель, издатель-подписчик)

Назначение паттерна Observer

  • Паттерн Observer определяет зависимость "один-ко-многим" между объектами так, что при изменении состояния одного объекта все зависящие от него объекты уведомляются и обновляются автоматически.
  • Паттерн Observer инкапсулирует главный (независимый) компонент в абстракцию Subject и изменяемые (зависимые) компоненты в иерархию Observer.
  • Паттерн Observer определяет часть "View" в модели Model-View-Controller (MVC) .

Паттерн Observer находит широкое применение в системах пользовательского интерфейса, в которых данные и их представления ("виды") отделены друг от друга. При изменении данных должны быть изменены все представления этих данных (например, в виде таблицы, графика и диаграммы).

Решаемая проблема

Имеется система, состоящая из множества взаимодействующих классов. При этом взаимодействующие объекты должны находиться в согласованных состояниях. Вы хотите избежать монолитности такой системы, сделав классы слабо связанными (или повторно используемыми).

Обсуждение паттерна Observer

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

Такая схема позволяет динамически настраивать количество и "типы" представлений объектов.

Описанный выше протокол взаимодействия соответствует модели вытягивания (pull), когда субъект информирует наблюдателей о своем изменении, и каждый наблюдатель ответственен за "вытягивание" у Subject нужных ему данных. Существует также модель проталкивания, когда субъект Subject посылает ("проталкивает") наблюдателям детальную информацию о своем изменении.

Существует также ряд вопросов, о которых следует упомянуть, но обсуждение которых останется за рамками данной статьи:

  • Реализация "компрессии" извещений (посылка единственного извещения на серию последовательных изменений субъекта Subject).
  • Мониторинг нескольких субъектов с помощью одного наблюдателя Observer.
  • Исключение висячих ссылок у наблюдателей на удаленные субъекты. Для этого субъект должен уведомить наблюдателей о своем удалении.

Паттерн Observer впервые был применен в архитектуре Model-View-Controller языка Smalltalk, представляющей каркас для построения пользовательских интерфейсов.

Структура паттерна Observer

Subject представляет главную (независимую) абстракцию. Observer представляет изменяемую (зависимую) абстракцию. Субъект извещает наблюдателей о своем изменении, на что каждый наблюдатель может запросить состояние субъекта.

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

Пример паттерна Observer

Паттерн Observer определяет зависимость "один-ко-многим" между объектами так, что при изменении состояния одного объекта все зависящие от него объекты уведомляются и обновляются автоматически. Некоторые аукционы демонстрируют этот паттерн. Каждый участник имеет карточку с цифрами, которую он использует для обозначения предлагаемой цены (ставки). Ведущий аукциона (Subject) начинает торги и наблюдает, когда кто-нибудь поднимает карточку, предлагая новую более высокую цену. Ведущий принимает заявку, о чем тут же извещает всех участников аукциона (Observers).

Использование паттерна Observer

  1. Проведите различия между основной (или независимой) и дополнительной (или зависимой) функциональностями.
  2. Смоделируйте "независимую" функциональность с помощью абстракции "субъект".
  3. Смоделируйте "зависимую" функциональность с помощью иерархии "наблюдатель".
  4. Класс Subjectсвязан только c базовым классом Observer.
  5. Клиент настраивает количество и типы наблюдателей.
  6. Наблюдатели регистрируются у субъекта.
  7. Субъект извещает всех зарегистрированных наблюдателей.
  8. Субъект может "протолкнуть" информацию в наблюдателей, или наблюдатели могут "вытянуть" необходимую им информацию от объекта Subject.

Особенности паттерна Observer

  • Паттерны Chain of Responsibility, Command, Mediator и Observer показывают, как можно разделить отправителей и получателей запросов с учетом своих особенностей. Chain of Responsibility передает запрос отправителя по цепочке потенциальных получателей. Command определяет связь - "оправитель-получатель" с помощью подкласса. В Mediator отправитель и получатель ссылаются друг на друга косвенно, через объект-посредник. В паттерне Observer связь между отправителем и получателем получается слабой, при этом число получателей может конфигурироваться во время выполнения.
  • Mediator и Observer являются конкурирующими паттернами. Если Observer распределяет взаимодействие c помощью объектов "наблюдатель" и "субъект", то Mediator использует объект-посредник для инкапсуляции взаимодействия между другими объектами. Мы обнаружили, что легче сделать повторно используемыми Наблюдателей и Субъектов, чем Посредников.
  • Mediator может использовать Observer для динамической регистрации коллег и их взаимодействия с посредником.

Реализация паттерна Observer

Реализация паттерна Observer по шагам

  1. Смоделируйте "независимую" функциональность с помощью абстракции "субъект".
  2. Смоделируйте "зависимую" функциональность с помощью иерархии "наблюдатель".
  3. Класс Subjectсвязан только c базовым классом Observer.
  4. Наблюдатели регистрируются у субъекта.
  5. Субъект извещает всех зарегистрированных наблюдателей.
  6. Наблюдатели "вытягивают" необходимую им информацию от объекта Subject.
  7. Клиент настраивает количество и типы наблюдателей.
#include <iostream>
#include <vector>
using namespace std;

// 1. "Независимая" функциональность
class Subject {
    // 3. Связь только базовым классом Observer
    vector < class Observer * > views; 
    int value;
  public:
    void attach(Observer *obs) {
        views.push_back(obs);
    }
    void setVal(int val) {
        value = val;
        notify();
    }
    int getVal() {
        return value;
    }
    void notify();
};

// 2. "Зависимая" функциональность
class Observer {
    Subject *model;
    int denom;
  public:
    Observer(Subject *mod, int div) {
        model = mod;
        denom = div;
        // 4. Наблюдатели регистрируются у субъекта
        model->attach(this);
    }
    virtual void update() = 0;
  protected:
    Subject *getSubject() {
        return model;
    }
    int getDivisor() {
        return denom;
    }
};

void Subject::notify() {
  // 5. Извещение наблюдателей
  for (int i = 0; i < views.size(); i++)
    views[i]->update();
}

class DivObserver: public Observer {
  public:
    DivObserver(Subject *mod, int div): Observer(mod, div){}
    void update() {
        // 6. "Вытягивание" интересующей информации
        int v = getSubject()->getVal(), d = getDivisor();
        cout << v << " div " << d << " is " << v/d << '\n';
    }
};

class ModObserver: public Observer {
  public:
    ModObserver(Subject *mod, int div): Observer(mod, div){}
    void update() {
        int v = getSubject()->getVal(), d = getDivisor();
        cout << v << " mod " << d << " is " << v%d << '\n';
    }
};

int main() {
  Subject subj;
  DivObserver divObs1(&subj, 4); // 7. Клиент настраивает число 
  DivObserver divObs2(&subj, 3); //    и типы наблюдателей
  ModObserver modObs3(&subj, 3);
  subj.setVal(14);
}

Вывод программы:

14 div 4 is 3 14 div 3 is 4 14 mod 3 is 2

Реализация паттерна Observer: до и после

До

Количество и типы "зависимых" объектов определяются классом Subject. Пользователь не имеет возможности влиять на эту конфигурацию.

class DivObserver
{
    int m_div;
  public:
    DivObserver(int div)
    {
        m_div = div;
    }
    void update(int val)
    {
        cout << val << " div " << m_div
             << " is " << val / m_div << '\n';
    }
};

class ModObserver
{
    int m_mod;
  public:
    ModObserver(int mod)
    {
        m_mod = mod;
    }
    void update(int val)
    {
        cout << val << " mod " << m_mod 
             << " is " << val % m_mod << '\n';
    }
};

class Subject
{
    int m_value;
    DivObserver m_div_obj;
    ModObserver m_mod_obj;
  public:
    Subject(): m_div_obj(4), m_mod_obj(3){}
    void set_value(int value)
    {
        m_value = value;
        notify();
    }
    void notify()
    {
        m_div_obj.update(m_value);
        m_mod_obj.update(m_value);
    }
};

int main()
{
  Subject subj;
  subj.set_value(14);
}

Вывод программы:

14 div 4 is 3 14 div 3 is 4 14 mod 3 is 2

После

Теперь класс Subject не связан с непосредственной настройкой числа и типов объектов Observer. Клиент установил два наблюдателя DivObserver и одного ModObserver.

class Observer
{
  public:
    virtual void update(int value) = 0;
};

class Subject
{
    int m_value;
    vector m_views;
  public:
    void attach(Observer *obs)
    {
        m_views.push_back(obs);
    }
    void set_val(int value)
    {
        m_value = value;
        notify();
    }
    void notify()
    {
        for (int i = 0; i < m_views.size(); ++i)
          m_views[i]->update(m_value);
    }
};

class DivObserver: public Observer
{
    int m_div;
  public:
    DivObserver(Subject *model, int div)
    {
        model->attach(this);
        m_div = div;
    }
     /* virtual */void update(int v)
    {
        cout << v << " div " << m_div << " is " << v / m_div << '\n';
    }
};

class ModObserver: public Observer
{
    int m_mod;
  public:
    ModObserver(Subject *model, int mod)
    {
        model->attach(this);
        m_mod = mod;
    }
     /* virtual */void update(int v)
    {
        cout << v << " mod " << m_mod << " is " << v % m_mod << '\n';
    }
};

int main()
{
  Subject subj;
  DivObserver divObs1(&subj, 4);
  DivObserver divObs2(&subj, 3);
  ModObserver modObs3(&subj, 3);
  subj.set_val(14);
}

Вывод программы:

14 div 4 is 3 14 div 3 is 4 14 mod 3 is 2

Источник: http://sourcemaking.com/design_patterns/observer/

results matching ""

    No results matching ""