Паттерн 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
- Проведите различия между основной (или независимой) и дополнительной (или зависимой) функциональностями.
- Смоделируйте "независимую" функциональность с помощью абстракции "субъект".
- Смоделируйте "зависимую" функциональность с помощью иерархии "наблюдатель".
- Класс
Subject
связан только c базовым классомObserver
. - Клиент настраивает количество и типы наблюдателей.
- Наблюдатели регистрируются у субъекта.
- Субъект извещает всех зарегистрированных наблюдателей.
- Субъект может "протолкнуть" информацию в наблюдателей, или наблюдатели могут "вытянуть" необходимую им информацию от объекта Subject.
Особенности паттерна Observer
- Паттерны Chain of Responsibility, Command, Mediator и Observer показывают, как можно разделить отправителей и получателей запросов с учетом своих особенностей. Chain of Responsibility передает запрос отправителя по цепочке потенциальных получателей. Command определяет связь - "оправитель-получатель" с помощью подкласса. В Mediator отправитель и получатель ссылаются друг на друга косвенно, через объект-посредник. В паттерне Observer связь между отправителем и получателем получается слабой, при этом число получателей может конфигурироваться во время выполнения.
- Mediator и Observer являются конкурирующими паттернами. Если Observer распределяет взаимодействие c помощью объектов "наблюдатель" и "субъект", то Mediator использует объект-посредник для инкапсуляции взаимодействия между другими объектами. Мы обнаружили, что легче сделать повторно используемыми Наблюдателей и Субъектов, чем Посредников.
- Mediator может использовать Observer для динамической регистрации коллег и их взаимодействия с посредником.
Реализация паттерна Observer
Реализация паттерна Observer по шагам
- Смоделируйте "независимую" функциональность с помощью абстракции "субъект".
- Смоделируйте "зависимую" функциональность с помощью иерархии "наблюдатель".
- Класс
Subject
связан только c базовым классомObserver
. - Наблюдатели регистрируются у субъекта.
- Субъект извещает всех зарегистрированных наблюдателей.
- Наблюдатели "вытягивают" необходимую им информацию от объекта Subject.
- Клиент настраивает количество и типы наблюдателей.
#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