如何在观察者中处理具有不同状态值类型的 Observables

2024-01-25

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

我们正在创建并实现一个 C++ 框架,以便在 Arduino 等环境中使用。

为此,我想使用观察者模式,其中任何对传感器状态变化感兴趣的组件(Observables)可以注册自己,并且它将通过 Observable 调用来获得这些更改的通知notification()观察者方法,以自身作为参数。

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

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

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

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

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


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

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


我之前的回答没有考虑到同一个观察者可能会注册不同的可观察量。我将尝试在这里给出完整的解决方案。该解决方案非常灵活且可扩展,但有点难以理解,因为它涉及模板元编程(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);
    }
};

我正在使用 unorderded_map,以便根据可观察对象的 id 调用正确的调度程序函数。确认这有效:

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

这工作得很好,但如果你现在尝试调整这个观察者,以便它可以监视 temp_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);
    }
};

这是可行的,但是仅仅添加一个额外的可观察值就需要大量重复。您还可以想象如果您想创建任何组合(即帐户+温度可观察值、时钟+临时可观察值等),会发生什么。它根本无法扩展。

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 思想,请查看亚历山德雷斯库.我读过的最好的书之一。

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在观察者中处理具有不同状态值类型的 Observables 的相关文章

随机推荐