Идея

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

Применительно к MVC

Применительно к паттерну Model-View-Controller можно переформулировать вышесказанное так: к наблюдаемой Модели подключаются виды-наблюдатели; Контроллер модели манипулирует ею, и при достижении некоторых состояний Модель вызывает метод update() своих наблюдателей, передавая в нём себя для последующего извлечения информации и отображения её в Видах.

Ниже я приведу простенькую реализацию данной идеи в классах IObserver и Observable, весьма похожих на своих тёзок в Java.

Схематичная UML диаграмма

Реализация

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