如何在Observer

时间:2018-01-02 20:16:25

标签: c++ design-patterns arduino observable

(首先是上下文和问题,帖子底部的骨架代码)

我们正在创建和实现一个C ++框架,以便在像Arduino这样的环境中使用。

为此,我想使用Observer模式,其中任何对传感器状态变化感兴趣的组件(Observables)都可以注册自己,并且Observable会调用{{1}来通知这些变化。 Observer的方法,将自身作为参数。

一个观察者可以观察多个Observable,反之亦然。

问题在于Observer需要提取Observable的当前状态并对其执行某些操作,并且此当前状态可以采用所有形式和大小,具体取决于Observable的特定传感器。

它当然可以是序数值,它们是有限的并且可以被编码出来,就像我在下面的代码中用方法notification()所做的那样,但它也可以是传感器特定的结构,即对于RealTimeClock,它提供了日期和时间值的结构。结构当然在编译时定义,并针对特定传感器进行修复。

我的问题:最优雅,未来修改的解决方案或模式是什么?

编辑:请注意,dynamic_cast<>由于Arduino限制,结构是不可能的

我创建了以下类层次结构(骨架代码):

getValueasInt()

6 个答案:

答案 0 :(得分: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 :(得分:1)

如果传感器类型的数量或多或少稳定(并且它是 - 在大多数情况下变化非常罕见) - 那么只需在Observer端准备以获得几种通知:

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类。

要在Observable端实现此机制 - 请使用double dispatch pattern

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); 
  }
};

在实践中如何运作,请参阅live demo

答案 2 :(得分:1)

这是我的看法。如果我理解正确,每个观察者都知道具体的观察者正在监测什么;问题是观察者只获得一个指向具体observable的基类指针,因此无法访问完整的接口。假设您可以使用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);
}

如您所见,每次创建新的observable时都不需要进行转换;包装类为您完成此操作。您可能面临的一个问题是将观察者注册到错误的可观察者身上;在这种情况下,static_cast会失败但你不会遇到编译问题。解决这个问题的一种方法是让observable公开一个标识它的字符串,并让观察者在注册自己时检查该字符串。希望它有所帮助。

答案 3 :(得分: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,以便根据observable的id调用正确的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 id)。

这很好但是如果你现在尝试调整这个观察者以便它可以监视temperature_observables,那么事情会变得混乱。你要么必须编辑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);
    }
};

这样可行,但只需添加一个额外的observable,就会有很多重复。您还可以想象如果您想要创建任何组合(即帐户+温度可观察,时钟+温度可观察等)会发生什么。它完全没有扩展。

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));
    }
};

combined_interface类继承自每个interface_unit,并允许我们将函数注册到地图,类似于我们之前对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想法,请查看Alexandrescu的Modern C++ design。我读过的最好的一个。

如果有任何不清楚的地方,请告诉我,我会编辑答案。

答案 4 :(得分:0)

它可能不是最优雅的解决方案,但以下是一个选项:定义可以保存任何类型数据的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;
}

Backbone事件和一般事件总线模式的启发。

答案 5 :(得分:0)

C ++中Observer模式的难点在于处理生命周期和取消注册。

您可以使用以下内容:

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();
}

Demo