Как обрабатывать Observables с различными типами значений состояния в Observer

(Контекст и вопрос сначала, скелетный код внизу поста)

Мы создаем и внедряем среду C ++ для использования в таких средах, как Arduino.

Для этого я хочу использовать шаблон Observer, где любой компонент заинтересован в изменении состояния датчиков (Observables) может зарегистрироваться, и он будет уведомлен об этих изменениях Наблюдательной notification() Метод Обозревателя с самим собой в качестве параметра.

Один наблюдатель может наблюдать несколько наблюдаемых, и наоборот.

Проблема заключается в том, что наблюдателю необходимо извлечь текущее состояние наблюдаемой и что-то с ним сделать, и это текущее состояние может принимать все формы и размеры, в зависимости от конкретного датчика, который является наблюдаемой.

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

Мой вопрос: Какое решение или шаблон наиболее элегантны и пригодны для будущего?

Изменить: Обратите внимание, что dynamic_cast<> конструкции невозможны из-за ограничений Arduino


Я создал следующую иерархию классов (скелетный код):

class SenseNode
{
public:
SenseNode() {};
SenseNode(uint8_t aNodeId): id(aNodeId) {}
virtual ~SenseNode() {}

uint8_t getId() { return id; };
private:
uint8_t id = 0;
};

class SenseStateNode : virtual public SenseNode
{
public:
SenseStateNode(uint8_t aNodeId) : SenseNode(aNodeId) {}
virtual ~SenseStateNode() {}

/** Return current node state interpreted as an integer. */
virtual int getValueAsInt();
};

class SenseObservable: public SenseStateNode
{
public:
SenseObservable(uint8_t aNodeId);
virtual ~SenseObservable();
/** Notify all interested observers of the change in state by calling Observer.notification(this) */
virtual void notifyObservers();
protected:
virtual void registerObserver(SenseObserver *);
virtual void unregisterObserver(SenseObserver *);
};

class SenseObserver: virtual public SenseNode
{
public:
SenseObserver() {};
virtual ~SenseObserver();

/** Called by an Observable that we are observing to inform us of a change in state */
virtual void notification(SenseObservable *observable) {
int v = observable->getValueAsInt(); // works like a charm
DateTime d = observable-> ????  // How should i solve this elegantly?
};
};

7

Решение

Если число типов датчиков более или менее стабильно (а это — изменения в большинстве случаев довольно редки) — тогда просто будьте готовы на стороне наблюдателя получить несколько видов уведомлений:

class Observer
{
public:
virtual void notify(SenseNode& node) {
// implement here general actions - like printing: not interested in this

}
virtual void notify(RealTimeClock& node) {
notify(static_cast<SenseNode&>(node));
// by default go to more general function
}
// and follow this pattern - for all nodes you want to handle
// add corresponding notify(T&) function
};

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

Для реализации этого механизма на наблюдаемой стороне — используйте шаблон двойной отправки:

class SenseNode {
public:
virtual void notifyObserver(Observer& observer) {
observer.notify(*this);
}
};

class RealTimeClock : public virtual SenseNode {
public:
virtual void notifyObserver(Observer& observer) {
observer.notify(*this);
// this will select proper Observer::notify(RealTimeClock&)
// because *this is RealTimeCLock
}
};class SenseObservable: public SenseStateNode
{
public:
virtual void notifyObservers() {
for (auto& observer : observers)
notifyObserver(observer);
}
};

Как это работает на практике, смотрите живое демо

1

Другие решения

Вы могли бы пойти с

class SenseStateNode
{
...
virtual ObservableValue& getValue(); //or pointer, comes with different tradeoffs
};

Таким образом, каждый SenseObservable может вернуть тип, полученный из ObservableValue, Затем вам просто нужно придумать пригодный для использования универсальный API для этого наблюдаемого значения.

Например, это может быть:

class SenseObservable
{
DateTime* asDateTime(); //returns NULL if not a date
float* asFloat(); //returns NULL if not a float

};

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

1

Вот мой дубль. Если я правильно понимаю, каждый наблюдатель знает, что конкретно наблюдает за мониторингом; проблема в том, что наблюдатель получает только указатель базового класса на конкретную наблюдаемую область и, следовательно, не может получить доступ ко всему интерфейсу. Предполагая, что вы можете использовать static_cast как и предполагалось в предыдущих ответах, моя идея состоит в том, чтобы создать дополнительный класс, который будет отвечать за приведение указателя базового класса к конкретному, тем самым предоставляя вам доступ к конкретному интерфейсу. В приведенном ниже коде используются имена, отличные от названий в вашем посте, но это иллюстрирует идею:

#include <vector>
#include <algorithm>
#include <iostream>

class observable;

class observer {

public:

virtual void notify(observable&) = 0;
};

// For simplicity, I will give some default implementation for storing the observers
class observable {

// assumping plain pointers
// leaving it to you to take of memory
std::vector<observer*> m_observers;

public:

observable() = default;

void notifyObservers() {
for(auto& obs : m_observers) obs->notify(*this);
}

void registerObserver(observer* x) {
m_observers.push_back(x);
}

void unregisterObserver(observer* x) {
// give your implementation here
}

virtual ~observable() = default;
};

// our first observable with its own interface
class clock_observable
: public observable {

int m_time;

public:

clock_observable(int time)
: m_time(time){}

void change_time() {
m_time++;
notifyObservers(); // notify observes of time change
}

int get_time() const {
return m_time;
}
};

// another observable
class account_observable
: public observable {

double m_balance;

public:

account_observable(double balance)
: m_balance(balance){}

void deposit_amount(double x) {
m_balance += x;
notifyObservers(); // notify observes of time change
}

int get_balance() const {
return m_balance;
}
};

// this wrapper will be inherited and allows you to access the interface of the concrete observable
// all concrete observers should inherit from this class
template <class Observable>
class observer_wrapper
: public observer {

virtual void notify_impl(Observable& x) = 0;

public:

void notify(observable& x) {
notify_impl(static_cast<Observable&>(x));
}
};

// our first clock_observer
class clock_observer1
: public observer_wrapper<clock_observable> {

void notify_impl(clock_observable& x) override {
std::cout << "clock_observer1 says time is " << x.get_time() << std::endl;
}
};

// our second clock_observer
class clock_observer2
: public observer_wrapper<clock_observable> {

void notify_impl(clock_observable& x) override {
std::cout << "clock_observer2 says time is " << x.get_time() << std::endl;
}
};

// our first account_observer
class account_observer1
: public observer_wrapper<account_observable> {

void notify_impl(account_observable& x) override {
std::cout << "account_observer1 says balance is " << x.get_balance() << std::endl;
}
};

// our second account_observer
class account_observer2
: public observer_wrapper<account_observable> {

void notify_impl(account_observable& x) override {
std::cout << "account_observer2 says balance is " << x.get_balance() << std::endl;
}
};int main() {

auto clock = new clock_observable(100);
auto account = new account_observable(100.0);

observer* clock_obs1 = new clock_observer1();
observer* clock_obs2 = new clock_observer2();

observer* account_obs1 = new account_observer1();
observer* account_obs2 = new account_observer2();

clock->registerObserver(clock_obs1);
clock->registerObserver(clock_obs2);

account->registerObserver(account_obs1);
account->registerObserver(account_obs2);

clock->change_time();
account->deposit_amount(10);
}

Как видите, вам не нужно кастовать каждый раз, когда вы создаете новую наблюдаемую; класс-обертка делает это за вас. Одна из проблем, с которой вы можете столкнуться — это регистрация наблюдателя в неправильной наблюдаемой области; в этом случае static_cast потерпит неудачу, но у вас не возникнет проблем с компиляцией. Один из способов обойти это — сделать так, чтобы наблюдаемое раскрыло строку, которая его идентифицировала, и наблюдатель проверил эту строку при регистрации. Надеюсь, поможет.

1

В моем предыдущем ответе не учитывалось, что один и тот же наблюдатель мог бы меня регистрировать с разными наблюдаемыми. Я постараюсь дать полное решение здесь. Решение очень гибкое и масштабируемое, но немного сложное для понимания, так как включает в себя шаблонное метапрограммирование (TMP). Я начну с описания того, как будет выглядеть конечный результат, а затем перейду к материалам TMP. Готовьтесь, это длинный ответ. Вот так:

Во-первых, для примера у нас есть три наблюдаемые, каждая со своим уникальным интерфейсом, к которому мы позже захотим обратиться от наблюдателя.

#include <vector>
#include <algorithm>
#include <iostream>
#include <unordered_map>
#include <string>

class observable;

class observer {

public:

virtual void notify(observable& x) = 0;
};

// For simplicity, I will give some default implementation for storing the observers
class observable {

// assumping plain pointers
// leaving it to you to take of memory
std::vector<observer*> m_observers;

public:

observable() = default;

// string id for identifying the concrete observable at runtime
virtual std::string id() = 0;

void notifyObservers() {
for(auto& obs : m_observers) obs->notify(*this);
}

void registerObserver(observer* x) {
m_observers.push_back(x);
}

void unregisterObserver(observer*) {
// give your implementation here
}

virtual ~observable() = default;
};

// our first observable with its own interface
class clock_observable
: public observable {

int m_time;

public:

clock_observable(int time)
: m_time(time){}

// we will use this later
static constexpr auto string_id() {
return "clock_observable";
}

std::string id() override {
return string_id();
}

void change_time() {
m_time++;
notifyObservers(); // notify observes of time change
}

int get_time() const {
return m_time;
}
};

// another observable
class account_observable
: public observable {

double m_balance;

public:

account_observable(double balance)
: m_balance(balance){}

// we will use this later
static constexpr auto string_id() {
return "account_observable";
}

std::string id() override {
return string_id();
}

void deposit_amount(double x) {
m_balance += x;
notifyObservers(); // notify observes of time change
}

int get_balance() const {
return m_balance;
}
};

class temperature_observable
: public observable {

double m_value;

public:

temperature_observable(double value)
: m_value(value){}

// we will use this later
static constexpr auto string_id() {
return "temperature_observable";
}

std::string id() override {
return string_id();
}

void increase_temperature(double x) {
m_value += x;
notifyObservers(); // notify observes of time change
}

int get_temperature() const {
return m_value;
}
};

Обратите внимание, что каждый наблюдатель предоставляет функцию id, возвращающую строку, которая ее идентифицирует. Теперь предположим, что мы хотим создать наблюдателя, который следит за часами и счетом. Мы могли бы иметь что-то вроде этого:

class simple_observer_clock_account
: public observer {

std::unordered_map<std::string, void (simple_observer_clock_account::*) (observable&)> m_map;

void notify_impl(clock_observable& x) {
std::cout << "observer says time is " << x.get_time() << std::endl;
}

void notify_impl(account_observable& x) {
std::cout << "observer says balance is " << x.get_balance() << std::endl;
}

// casts the observable into the concrete type and passes it to the notify_impl
template <class X>
void dispatcher_function(observable& x) {
auto& concrete = static_cast<X&>(x);
notify_impl(concrete);
}

public:

simple_observer_clock_account() {
m_map[clock_observable::string_id()] = &simple_observer_clock_account::dispatcher_function<clock_observable>;
m_map[account_observable::string_id()] = &simple_observer_clock_account::dispatcher_function<account_observable>;
}

void notify(observable& x) override {
auto f = m_map.at(x.id());
(this->*f)(x);
}
};

Я использую unoderded_map, так что будет вызываться правильная функция dispatcher_function в зависимости от идентификатора наблюдаемой. Подтвердите, что это работает:

int main() {

auto clock = new clock_observable(100);
auto account = new account_observable(100.0);

auto obs1 = new simple_observer_clock_account();

clock->registerObserver(obs1);
account->registerObserver(obs1);

clock->change_time();
account->deposit_amount(10);
}

Хорошая вещь в этой реализации состоит в том, что если вы попытаетесь зарегистрировать наблюдателя в Temperature_observable, вы получите исключение времени выполнения (так как m_map не будет содержать соответствующий идентификатор Temperature_observable).

Это прекрасно работает, но если вы попытаетесь настроить этого наблюдателя так, чтобы он мог отслеживать температуру_обсерватории, все становится не так. Вы должны либо отредактировать simple_observer_clock_account (который идет вразрез с закрытым для модификации, открытым для принципа расширения), либо создать нового наблюдателя следующим образом:

class simple_observer_clock_account_temperature
: public observer {

std::unordered_map<std::string, void (simple_observer_clock_account_temperature::*) (observable&)> m_map;

// repetition
void notify_impl(clock_observable& x) {
std::cout << "observer1 says time is " << x.get_time() << std::endl;
}

// repetition
void notify_impl(account_observable& x) {
std::cout << "observer1 says balance is " << x.get_balance() << std::endl;
}

// genuine addition
void notify_impl(temperature_observable& x) {
std::cout << "observer1 says temperature is " << x.get_temperature() << std::endl;
}

// repetition
template <class X>
void dispatcher_function(observable& x) {
auto& concrete = static_cast<X&>(x);
notify_impl(concrete);
}

public:

// lots of repetition only to add an extra observable
simple_observer_clock_account_temperature() {
m_map[clock_observable::string_id()] = &simple_observer_clock_account_temperature::dispatcher_function<clock_observable>;
m_map[account_observable::string_id()] = &simple_observer_clock_account_temperature::dispatcher_function<account_observable>;
m_map[temperature_observable::string_id()] = &simple_observer_clock_account_temperature::dispatcher_function<temperature_observable>;
}

void notify(observable& x) override {
auto f = m_map.at(x.id());
(this->*f)(x);
}
};

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

Решение TMP, по сути, предоставляет способ сделать все вышеперечисленное автоматически и повторно использовать переопределенные реализации, а не повторять их снова и снова. Вот как это работает:

Мы хотим построить иерархию классов, где базовый класс будет представлять ряд виртуальных notify_impl(T&) метод, по одному для каждого T конкретный наблюдаемый тип, который мы хотим наблюдать. Это достигается следующим образом:

template <class Observable>
class interface_unit {

public:

virtual void notify_impl(Observable&) = 0;
};

// combined_interface<T1, T2, T3> would result in a class with the following members:
// notify_impl(T1&)
// notify_impl(T2&)
// notify_impl(T3&)
template <class... Observable>
class combined_interface
: public interface_unit<Observable>...{

using self_type = combined_interface<Observable...>;
using dispatcher_type = void (self_type::*)(observable&);
std::unordered_map<std::string, dispatcher_type> m_map;

public:

void map_register(std::string s, dispatcher_type dispatcher) {
m_map[s] = dispatcher;
}

auto get_dispatcher(std::string s) {
return m_map.at(s);
}

template <class X>
void notify_impl(observable& x) {
interface_unit<X>& unit = *this;
// transform the observable to the concrete type and pass to the relevant interface_unit.
unit.notify_impl(static_cast<X&>(x));
}
};

Класс комбинированный_интерфейс наследует от каждого интерфейсного элемента и также позволяет нам регистрировать функции на карте, аналогично тому, что мы делали ранее для simple_observer_clock_account. Теперь нам нужно создать рекурсивную иерархию, где на каждом шаге рекурсии мы переопределяем notify_impl(T&) для каждого T мы заинтересованы в.

// forward declaration
// Iface will be combined_interface<T1, T2>
// The purpose of this class is to implement the virtual methods found in the Iface class, ie notify_impl(T1&), notify_impl(T2&)
// Each ImplUnit provides an override for a single notify_impl(T&)
// Root is the base class of the hierarchy; this will be the data (if any) held by the observer
template <class Root, class Iface, template <class, class> class... ImplUnits>
struct hierarchy;

// recursive
template <class Root, class Iface, template <class, class> class ImplUnit, template <class, class> class... ImplUnits>
struct hierarchy<Root, Iface, ImplUnit, ImplUnits...>
: public ImplUnit< hierarchy<Root, Iface, ImplUnits...>, Root > {

using self_type = hierarchy<Root, Iface, ImplUnit, ImplUnits...>;
using base_type = ImplUnit< hierarchy<Root, Iface, ImplUnits...>, Root >;

public:

template <class... Args>
hierarchy(Args&&... args)
: base_type{std::forward<Args>(args)...} {

using observable_type = typename base_type::observable_type;
Iface::map_register(observable_type::string_id(), &Iface::template notify_impl<observable_type>);
}
};

// specialise if we have iterated through all ImplUnits
template <class Root, class Iface>
struct hierarchy<Root, Iface>
: public Root
, public observer
, public Iface {

public:

template <class... Args>
hierarchy(Args&&... args)
: Root(std::forward<Args>(args)...)
, Iface(){}
};

На каждом шаге рекурсии мы регистрируем функцию dispatcher_function на нашей карте.

Наконец, мы создаем класс, который будет использоваться для наших наблюдателей:

template <class Root, class Iface, template <class, class> class... ImplUnits>
class observer_base
: public hierarchy<Root, Iface, ImplUnits...> {

public:

using base_type = hierarchy<Root, Iface, ImplUnits...>;

void notify(observable& x) override {
auto f = this->get_dispatcher(x.id());
return (this->*f)(x);
}

template <class... Args>
observer_base(Args&&... args)
: base_type(std::forward<Args>(args)...) {}
};

Давайте теперь создадим некоторые наблюдаемые. Для простоты я предполагаю, что у наблюдателя нет данных:

class observer1_data {};

// this is the ImplUnit for notify_impl(clock_observable&)
// all such implementations must inherit from the Super argument and expose the observable_type type member
template <class Super, class ObserverData>
class clock_impl
: public Super {

public:

using Super::Super;
using observable_type = clock_observable;

void notify_impl(clock_observable& x) override {
std::cout << "observer says time is " << x.get_time() << std::endl;
}
};

template <class Super, class ObserverdData>
class account_impl
: public Super {

public:

using Super::Super;
using observable_type = account_observable;

void notify_impl(account_observable& x) override {
std::cout << "observer says balance is " << x.get_balance() << std::endl;
}
};

template <class Super, class ObserverdData>
class temperature_impl
: public Super {

public:

using Super::Super;
using observable_type = temperature_observable;

void notify_impl(temperature_observable& x) override {
std::cout << "observer says temperature is " << x.get_temperature() << std::endl;
}
};

Теперь мы можем легко создать любого наблюдателя, которого мы хотим, независимо от того, какие комбинации мы хотим использовать:

using observer_clock =  observer_base<observer1_data,
combined_interface<clock_observable>,
clock_impl>;

using observer_clock_account =  observer_base<observer1_data,
combined_interface<clock_observable, account_observable>,
clock_impl, account_impl>;

using observer_clock_account_temperature =  observer_base<observer1_data,
combined_interface<clock_observable, account_observable, temperature_observable>,
clock_impl, account_impl, temperature_impl>;

int main() {

auto clock = new clock_observable(100);
auto account = new account_observable(100.0);
auto temp = new temperature_observable(36.6);

auto obs1 = new observer_clock_account_temperature();

clock->registerObserver(obs1);
account->registerObserver(obs1);
temp->registerObserver(obs1);clock->change_time();
account->deposit_amount(10);
temp->increase_temperature(2);
}

Я могу оценить, что есть много, чтобы переварить. Во всяком случае, я надеюсь, что это полезно. Если вы хотите понять в деталях идеи TMP выше, взгляните на Современный C ++ дизайн Александреску. Один из лучших, которые я читал.

Дайте мне знать, если что-то не понятно, и я отредактирую ответ.

1

Это может быть не самое элегантное решение, но вариант является следующим: определить EventArgs структура, которая может содержать любые данные, а затем выполнить приведение EventHandlers, Вот фрагмент кода, который я только что написал (но не носитель языка CPP):

#include <iostream>
#include <map>
#include <vector>

using namespace std;

struct EventArgs;

typedef void (*EventHandler)(EventArgs args);
typedef std::vector<EventHandler> BunchOfHandlers;
typedef std::map<string, BunchOfHandlers> HandlersBySubject;

struct EventArgs
{
void* data;

EventArgs(void* data)
{
this->data = data;
}
};

class AppEvents
{
HandlersBySubject handlersBySubject;

public:
AppEvents()
{
}

void defineSubject(string subject)
{
handlersBySubject[subject] = BunchOfHandlers();
}

void on(string subject, EventHandler handler)
{
handlersBySubject[subject].push_back(handler);
}

void trigger(string subject, EventArgs args)
{
BunchOfHandlers& handlers = handlersBySubject[subject];

for (const EventHandler& handler : handlers)
{
handler(args);
}
}
};

struct FooData
{
int x = 42;
string str = "Test";
};

struct BarData
{
long y = 123;
char c = 'x';
};

void foo_handler_a(EventArgs args)
{
FooData* data = (FooData*)args.data;
cout << "foo_handler_a: " << data->x << " " << data->str << endl;
}

void foo_handler_b(EventArgs args)
{
FooData* data = (FooData*)args.data;
cout << "foo_handler_b: " << data->x << " " << data->str << endl;
}

void bar_handler_a(EventArgs args)
{
BarData* data = (BarData*)args.data;
cout << "bar_handler_a: " << data->y << " " << data->c << endl;
}

void bar_handler_b(EventArgs args)
{
BarData* data = (BarData*)args.data;
cout << "bar_handler_b: " << data->y << " " << data->c << endl;
}

int main()
{
AppEvents* events = new AppEvents();

events->defineSubject("foo");
events->defineSubject("bar");

events->on("foo", foo_handler_a);
events->on("foo", foo_handler_a);
events->on("bar", bar_handler_b);
events->on("bar", bar_handler_b);

events->trigger("foo", EventArgs(new FooData()));
events->trigger("bar", EventArgs(new BarData()));

return 0;
}

Вдохновленный позвоночник события и общее Event Bus шаблон.

0

Сложность Observer Pattern в C ++ состоит в том, чтобы обрабатывать пожизненно и не регистрироваться.

Вы можете использовать следующее:

class Observer;

class IObserverNotifier
{
public:
virtual ~IObserverNotifier() = default;
virtual void UnRegister(Observer&) = 0;
};

class Observer
{
public:
explicit Observer() = default;
virtual ~Observer() {
for (auto* abstractObserverNotifier : mAbstractObserverNotifiers)
abstractObserverNotifier->UnRegister(*this);
}

Observer(const Observer&) = delete;
Observer(Observer&&) = delete;
Observer& operator=(const Observer&) = delete;
Observer& operator=(Observer&&) = delete;

void AddObserverNotifier(IObserverNotifier& observerNotifier)
{
mAbstractObserverNotifiers.insert(&observerNotifier);
}

void RemoveObserverNotifier(IObserverNotifier& observerNotifier)
{
mAbstractObserverNotifiers.erase(&observerNotifier);
}

private:
std::set<IObserverNotifier*> mAbstractObserverNotifiers;
};

template<typename ... Params>
class ObserverNotifier : private IObserverNotifier
{
public:
ObserverNotifier() = default;
~ObserverNotifier() {
for (const auto& p : mObserverCallbacks) {
p.first->RemoveObserverNotifier(*this);
}
}

ObserverNotifier(const ObserverNotifier&) = delete;
ObserverNotifier(ObserverNotifier&&) = delete;

ObserverNotifier& operator=(const ObserverNotifier&) = delete;
ObserverNotifier& operator=(ObserverNotifier&&) = delete;

void Register(Observer& observer, std::function<void(Params...)> f) {
mObserverCallbacks.emplace_back(&observer, f);
observer.AddObserverNotifier(*this);
}

void NotifyObservers(Params... args) const
{
for (const auto& p : mObserverCallbacks)
{
const auto& callback = p.second;

callback(args...);
}
}

void UnRegister(Observer& observer) override
{
mObserverCallbacks.erase(std::remove_if(mObserverCallbacks.begin(),
mObserverCallbacks.end(),
[&](const auto& p) { return p.first == &observer;}),
mObserverCallbacks.end());
}

private:
std::vector<std::pair<Observer*, std::function<void(Params...)>>> mObserverCallbacks;
};

И тогда использование будет что-то вроде:

class Sensor
{
public:
void ChangeTime() {
++mTime;
mOnTimeChange.NotifyObservers(mTime);
}

void ChangeTemperature(double delta) {
mTemperature += delta;
mOnTemperatureChange.NotifyObservers(mTemperature);
}

void RegisterTimeChange(Observer& observer, std::function<void(double)> f) { mOnTimeChange.Register(observer, f); }
void RegisterTemperatureChange(Observer& observer, std::function<void(double)> f) { mOnTemperatureChange.Register(observer, f); }

private:
ObserverNotifier<int> mOnTimeChange;
ObserverNotifier<double> mOnTemperatureChange;
int mTime = 0;
double mTemperature = 0;
};

class Ice : public Observer {
public:

void OnTimeChanged(int time) {
mVolume -= mLose;
mOnVolumeChange.NotifyObservers(mVolume);
}

void OnTemperatureChanged(double t) {
if (t <= 0) {
mLose = 0;
} else if (t < 15) {
mLose = 5;
} else {
mLose = 21;
}
}

void RegisterVolumeChange(Observer& observer, std::function<void(double)> f) { mOnVolumeChange.Register(observer, f); }private:
ObserverNotifier<double> mOnVolumeChange;
double mVolume = 42;
double mLose = 0;
};

class MyObserver : public Observer {
public:
static void OnTimeChange(int t) {
std::cout << "observer says time is " << t << std::endl;
}

static void OnTemperatureChange(double temperature) {
std::cout << "observer says temperature is " << temperature << std::endl;
}

static void OnIceChange(double volume) {
std::cout << "observer says Ice volume is " << volume << std::endl;
}

};

И проверить это:

int main()
{
Sensor sensor;
Ice ice;
MyObserver observer;

sensor.RegisterTimeChange(observer, &MyObserver::OnTimeChange);
sensor.RegisterTemperatureChange(observer, &MyObserver::OnTemperatureChange);
ice.RegisterVolumeChange(observer, &MyObserver::OnIceChange);
sensor.RegisterTimeChange(ice, [&](int t){ice.OnTimeChanged(t);});
sensor.RegisterTemperatureChange(ice, [&](double t){ice.OnTemperatureChanged(t);});

sensor.ChangeTemperature(0);
sensor.ChangeTime();
sensor.ChangeTemperature(10.3);
sensor.ChangeTime();
sensor.ChangeTime();
sensor.ChangeTemperature(42.1);
sensor.ChangeTime();
}

демонстрация

0
По вопросам рекламы [email protected]