Реализация паттерна проектирования Observer на С++17
Идея⌗
Паттерн проектирования Observer (наблюдатель) является классическим и элегантным маханизмом динамического оповещения одних объектов об обновлениях состояния других объектов. В нём один или несколько объектов-наблюдателей неким образом подключается к объекту-наблюдаемому, будто-бы подписываясь на его обновления. Наблюдаемое же ничего не знает о своих подписчиках и вызывает их общий метод, когда посчитает нужным, обычно передавая в качестве параметра указатель на самого себя (т.е. на объект-наблюдатель). Таким образом подключенные конкретные наблюдатели асинхронно получают управление и манипулируют конкретным наблюдаемым, полученным во входном параметре.
Применительно к MVC⌗
Применительно к паттерну Model-View-Controller можно переформулировать
вышесказанное так: к наблюдаемой Модели подключаются виды-наблюдатели;
Контроллер модели манипулирует ею, и при достижении некоторых состояний Модель
вызывает метод update()
своих наблюдателей, передавая в нём себя для
последующего извлечения информации и отображения её в Видах.
Ниже я приведу простенькую реализацию данной идеи в классах IObserver
и
Observable
, весьма похожих на своих тёзок в Java.
Реализация⌗
Observer⌗
Для наблюдателей создадим чистый виртуальный класс (интерфейс):
#ifndef IOBSERVER_HH
#define IOBSERVER_HH
#include <memory>
#include <any>
class Observable;
class IObserver {
public:
virtual void update(std::shared_ptr<Observable> o, std::any aux) = 0;
virtual ~IObserver() = default;
};
#endif // IOBSERVER_HH
IObservable⌗
А вот наблюдаемое. Все Модели будут наследоваться от этого класса:
#ifndef OBSERVABLE_HH
#define OBSERVABLE_HH
#include "IObserver.hh"
#include <vector>
#include <memory>
#include <any>
class Observable
{
private:
std::vector<std::shared_ptr<IObserver>> observers;
bool changed;
protected:
void clearChanged();
void setChanged();
public:
Observable();
~Observable();
void addObserver(std::shared_ptr<IObserver> o);
size_t countObservers();
void deleteObservers();
void deleteObserver(std::shared_ptr<IObserver> o);
bool hasChanged();
void notifyObservers(std::any aux = nullptr);
};
#endif // OBSERVABLE_HH
Его имплементация:
#include "Observable.hh"
void Observable::clearChanged()
{
changed = false;
}
void Observable::setChanged()
{
changed = true;
}
Observable::Observable()
: changed(false)
{
}
void Observable::addObserver(std::shared_ptr<IObserver> o)
{
observers.push_back(o);
}
size_t Observable::countObservers()
{
return observers.size();
}
void Observable::deleteObservers()
{
observers.clear();
}
void Observable::deleteObserver(std::shared_ptr<IObserver> o)
{
for (auto it = observers.begin(); it != observers.end(); it++)
if (it->get() == o.get()) {
observers.erase(it);
}
}
bool Observable::hasChanged()
{
return changed;
}
void Observable::notifyObservers(std::any aux)
{
if (hasChanged()) {
if (! observers.empty()) {
for (auto o : observers)
o->update(std::shared_ptr<Observable>(this), aux);
}
clearChanged();
}
}
Всё довольно просто. Мы храними наблюдателей в std::vector
и используем C++17
std::any
для опционального параметра notifyObservers()
(этот метод и будет
вызываться наследником-моделью).
Стоит обратить внимание на тот факт, что модель не будет уведомлять
наблюдателей, если в ней не установлен влаг changed
методом setChanged()
(защищённый метод, концептуально доступный только из классов-потомков: внешние
объекты не могут решать, обновлена ли модель или нет).
Тест⌗
В качестве теста создадим две модели и заимплиментим два наблюдателя:
#include <iostream>
#include "Observable.hh"
#include "IObserver.hh"
class MyModel1 : public Observable {
private:
int value1;
int value2;
int result;
public:
void setValue1(int value) {
this->value1 = value;
setChanged();
}
void setValue2(int value) {
this->value2 = value;
setChanged();
}
int getResult() {
return result;
}
void doCalculation() {
result = value1 + value2;
notifyObservers();
}
};
class MyModel2 : public Observable {
private:
std::string message;
public:
void broadcast(const std::string& str) {
message = str;
setChanged();
notifyObservers(str);
}
};
class MyView1 : public IObserver {
// IObserver interface
public:
void update(std::shared_ptr<Observable> o, std::any aux) override {
auto model = std::static_pointer_cast<MyModel1>(o);
std::cout << "MyView1: the model is updated! model->getResult() is "
<< model->getResult() << std::endl;
}
};
class MyView2 : public IObserver {
// IObserver interface
public:
void update(std::shared_ptr<Observable> o, std::any aux) override
{
auto model = std::static_pointer_cast<MyModel2>(o);
std::string data = std::any_cast<std::string>(aux);
std::cout << "MyView2: the model is updated! Broadcasted aux is: "
<< data << std::endl;
}
};
int main()
{
using namespace std;
auto model1 = make_shared<MyModel1>();
auto model2 = make_shared<MyModel2>();
auto view1 = make_shared<MyView1>();
auto view2 = make_shared<MyView2>();
/*
* Подключаем наблюдателей к нашим моделям
*/
model1->addObserver(view1);
model2->addObserver(view2);
/*
* Манипулируем моделями, будто мы-контроллеры
*/
model1->setValue1(123);
string str("I'm a std::string object copied into the on-heap model");
model2->broadcast(str);
// на model1 уже выставлен влаг "обновлена", но уведомление произойдёт только
// в doCalculation()
model1->setValue2(177);
model1->doCalculation();
// отключаем наблюдателя от модели
model2->deleteObservers();
// теперь view1 не триггерится
model2->broadcast(str);
return 0;
}
Результат:
MyView2: the model is updated! Broadcasted aux is: I'm a std::string object copied into the on-heap model
MyView1: the model is updated! model->getResult() is 300