【问题标题】:Observer pattern with different notifications具有不同通知的观察者模式
【发布时间】:2014-11-09 22:11:06
【问题描述】:

我正在尝试创建一个观察者模式,主题通过不同的通知通知观察者。

通常在观察者模式实现中,您只能看到一个名为 notify 的方法,它通知观察者某事发生了,并且有一种反转,观察者持有主体的指针,并在收到通知时向主体询问某事.

我的实现有点不同,主体附加观察者并通知所有观察者,而不需要在观察者内部持有主体的指针。例如:

#include <iostream>
#include <vector>

class ObserverEvents
{
public:
    virtual addSomethingOne(int) = 0;
    virtual addSomethingTwo(float) = 0;
};

class Observer : public ObserverEvents
{
public:
    Observer();
    ~Observer();

    virtual addSomethingOne(int) {}
    virtual addSomethingTwo(float) {}
};

class Subject : public ObserverEvents
{
public:
    Subject() {}

    ~Subject()
    {
        for (int i = 0; i < observers.size(); ++i)
        {
            delete observers[i];
        }
    }

    void attach(Observer * observer)
    {
        observers.push_back(observer);
    }

    virtual addSomethingOne(int something)
    {
        for (int i = 0; i < observers.size(); ++i)
        {
            observers[i].addSomethingOne(something);
        }
    }

    virtual addSomethingTwo(float something)
    {
        for (int i = 0; i < observers.size(); ++i)
        {
            observers[i].addSomethingTwo(something);
        }
    }
private:
    std::vector<Observer *> observers;
};

class FooObserver : public Observer
{
public:
    BarObserver() {}
    ~BarObserver() {}

    addSomethingOne(int something)
    {
        // do something with something
    }

    addSomethingTwo(float something)
    {
        // do something with something
    }
};

class BarObserver : public Observer
{
public:
    BizObserver() {}
    ~BizObserver() {}

    addSomethingOne(int something)
    {
        // do something with something
    }

    addSomethingTwo(float something)
    {
        // do something with something
    }
};

int main(int argc, char const * argv[])
{
    Subject subject;
    subject.attach(new FooObserver());
    subject.attach(new BarObserver());

    return 0;
}

我唯一担心的是,如果我没有违反任何设计原则,如 Open 和 Closed 或类似的东西,以及我是否需要添加一个我需要在所有其他类中实现的新通知。 (这很痛苦 - 想象一下 10 个甚至更多的观察者)。

我正在考虑让这个不同,只创建一个接口,然后我可以继承它来创建其他通知,但是有一个问题,观察者如何确定每种不同类型的通知是什么?

例子:

#include <iostream>
#include <vector>

class Notifier
{
public:
    Notifier() {}
    ~Notifier() {}

    virtual int getInt() const = 0;
};

class FooNotifier
{
public:
    FooNotifier() {}
    ~FooNotifier() {}

    int getInt() const
    {
        return 10;
    }
};

class BarNotifier
{
public:
    BarNotifier() {}
    ~BarNotifier() {}

    int getInt() const
    {
        return 50;
    }
};

class Observer : public ObserverEvents
{
public:
    Observer();
    ~Observer();

    virtual receive(Notifier *) = 0;
};

class Subject : public ObserverEvents
{
public:
    Subject() {}

    ~Subject()
    {
        for (int i = 0; i < observers.size(); ++i)
        {
            delete observers[i];
        }
    }

    void attach(Observer * observer)
    {
        observers.push_back(observer);
    }

    virtual notify(Notifier * notification)
    {
        for (int i = 0; i < observers.size(); ++i)
        {
            observers[i].receive(notification);
        }
    }
private:
    std::vector<Observer *> observers;
};

class FooObserver : public Observer
{
public:
    BarObserver() {}
    ~BarObserver() {}

    receive(Notifier * notification)
    {
        // ...
    }
};

class BarObserver : public Observer
{
public:
    BizObserver() {}
    ~BizObserver() {}

    receive(Notifier * notification)
    {
        // ...
    }
};

int main(int argc, char const * argv[])
{
    Subject subject;
    subject.attach(new FooObserver());
    subject.attach(new BarObserver());

    subject.notify(new FooNotifier());
    subject.notify(new BarNotifier());

    return 0;
}

实现只是一个示例,我知道我可以使用智能指针、删除原始指针并做得更好,但这只是一个实现示例。

这种新方法的问题在于,我需要知道Notifier 的 API 是否才能在 Observer 中使用它,才能调用 getInt

我该怎么做,最好的方法是什么?如何向观察者发送不同的通知?

【问题讨论】:

  • 看来你走的是 Java 路。 Java 以自己的方式做事,是因为语言非常受限制,而不是因为它好。我们有指针、lambda 和仿函数。
  • 其实我也不知道,我以前没用过Java。

标签: c++ design-patterns observer-pattern design-principles


【解决方案1】:
template<class C> class event {
    C list;
public:
    template<class... ARGS> inline void add(ARGS&&... args)
    {list.emplace_back(std::forward<ARGS>(args)...);}
    template<class... ARGS> inline void notifyAll(ARGS&&... args)
    {for(auto&& x : list) x(args...);}
};

一个事件的小模板,只支持添加监听并全部调用。

实例化

event<std::vector<void(*)()>> a;
event<std::vector<std::function<void()>>> a;

或与任何其他可调用容器一起使用。

一个有用的更改是使用std::tuple&lt;tag, callable&gt; 的容器,其中标签必须在添加时给出,并且可用于删除侦听器。

template<class F, class T=void*> class event {
    std::vector<std::pair<T, F>> v;
public:
    template<class... ARGS> inline void add(T tag, ARGS&&... args)
    {v.emplace_back(std::piecewise_construct, std::make_tuple(tag),
         std::forward_as_tuple(std::forward<ARGS>(args)...));}
    template<class... ARGS> inline void notifyAll(ARGS&&... args)
    {for(auto&& x : v) x(args...);}
    void remove(T tag) {v.erase(std::remove_if(v.begin(), v.end(),
        [=](const std::pair<T, F>& x){return x.first()==tag;}), v.end());}
};

后者使用仿函数类型实例化,例如std::function&lt;...&gt; 或函数指针。

【讨论】:

  • @Yakk:更正了问题。
  • 您的代码在某些情况下很好用,但我认为它还没有用。如果您理解我上面所说的,我希望主题收到不同类型的通知。想象一下这种情况:一个通知Animal我可以固有地制作CatAnimal。在动物中,我有三种可能的情况:死亡、活着和生病(只是一个例子)。我想做的是:当观察者/听众收到其中一个通知时,就知道动物是死了、活着还是生病了。 - 正如我上面所说,有两种方法,第一种是为每个状态创建一个方法...
  • 另一个是按照我所说的为每个带有状态的通知创建一个对象。问题是:如果我添加一个新状态会发生什么?想象一下使用 switch 来检测哪个状态是动物。这将打破开放和封闭。对? - 这就是问题:在不违反任何原则的情况下传递通知并在观察者/侦听器中了解该通知的状态/情况的最佳方式。
  • 你可以决定每个事件传递哪些参数,当然你也可以有多个事件。这两个中哪一个适合您的情况,由您决定。此外,“继承”在通知的上下文中并没有真正意义。这意味着什么?
  • 我知道,但是为每种情况创建一个方法,例如:notifyCatDeadnotifyCatSick,并且为每个新状态创建一个方法会很痛苦,因为我需要在每个主题和观察者中实现。 - 使用持有该状态的对象,并且侦听器/观察者试图知道该对象的状态似乎不那么痛苦,但似乎也违反了规则,因为要知道对象的状态,我需要使用 switch 或 if。
【解决方案2】:

新标准中的可变参数模板是解决您的问题的完美方式。您可以在此处查看带有模板的观察者模式实现:https://github.com/fnz/ObserverManager

【讨论】:

    【解决方案3】:

    侦听器需要从根本上知道他们将被调用的签名。捆绑消息集很有用,但有时值得。考虑这个接口:

    template<class...Args>
    struct broadcaster {
      template<class F> // F:Args...->vpid
      std::shared_ptr<void> attach( F&& f );
      size_t operator()(Args... args)const;
    };
    

    现在侦听器只需将一个可调用对象传递给广播器,然后在他们想要收听时存储一个共享的 ptr。当共享 ptr 过期时,tue 消息将停止发送。

    发送者使用参数调用广播者。

    template<class...Args>
    struct broadcaster {
      template<class F> // F:Args...->vpid
      std::shared_ptr<void> attach( F&& f ){
        auto x = std::make_shared<std::function<void(Args...)>>(std::forward<F>(f)));
        funcs.push_back(x);
        return x;
      }
      size_t operator()(Args... args)const{
        funcs.erase(
          std::remove_if(begin(funcs),end(funcs),
            [](auto&&f){return f.expired();}
          )
          ,end(funcs)
        );
        aito fs=funcs;
        for(auto&&f:fs){
          if(auto f_=f.lock())
            f_(args...);
        }
      }
    public:
      mutable std::vector<std::weak_ptr<std::function<void(Args...)>>> funcs;
    };
    

    这是一口。

    这确实意味着您的两种方法是解耦的。但它们可以用一个简单的 lambda 连接起来。在监听类中存储一个std::vector&lt;std::shared_ptr&lt;void&gt;&gt; 以处理 l8fetime 管理问题。

    【讨论】:

    • 两点:1、为什么要坚持新建shared_ptr?无论如何,最好允许通过一个已经拥有所需寿命的人。 2. 调用时不需要对监听器进行两次迭代,一次就足够了。如果您愿意,这也允许您询问侦听器是否应该再次调用它。
    • @dedup 以上允许在广播时添加侦听器而不会出现顺序问题(或对列表进行其他更改)。在多线程代码中,您在迭代侦听器副本之前释放锁。无论如何,该列表可能很短(10 或 100 不是 1000 或数百万)。至于预先存在的智能指针,稍作改动即可。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-09
    • 1970-01-01
    • 2019-09-18
    • 2015-01-31
    相关资源
    最近更新 更多