(Контекст и вопрос сначала, скелетный код внизу поста)
Мы создаем и внедряем среду 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?
};
};
Если число типов датчиков более или менее стабильно (а это — изменения в большинстве случаев довольно редки) — тогда просто будьте готовы на стороне наблюдателя получить несколько видов уведомлений:
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);
}
};
Как это работает на практике, смотрите живое демо
Вы могли бы пойти с
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 для различных наблюдаемых значений. Кроме того, вы должны вернуть их по указателю или по ссылке, чтобы не разрезать их. Затем либо пользователь, либо владелец должен управлять памятью.
Вот мой дубль. Если я правильно понимаю, каждый наблюдатель знает, что конкретно наблюдает за мониторингом; проблема в том, что наблюдатель получает только указатель базового класса на конкретную наблюдаемую область и, следовательно, не может получить доступ ко всему интерфейсу. Предполагая, что вы можете использовать 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 потерпит неудачу, но у вас не возникнет проблем с компиляцией. Один из способов обойти это — сделать так, чтобы наблюдаемое раскрыло строку, которая его идентифицировала, и наблюдатель проверил эту строку при регистрации. Надеюсь, поможет.
В моем предыдущем ответе не учитывалось, что один и тот же наблюдатель мог бы меня регистрировать с разными наблюдаемыми. Я постараюсь дать полное решение здесь. Решение очень гибкое и масштабируемое, но немного сложное для понимания, так как включает в себя шаблонное метапрограммирование (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 ++ дизайн Александреску. Один из лучших, которые я читал.
Дайте мне знать, если что-то не понятно, и я отредактирую ответ.
Это может быть не самое элегантное решение, но вариант является следующим: определить 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 шаблон.
Сложность 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();
}