如何将数据传递给“通用”观察者?作为参数还是单个结构?

时间:2010-08-30 15:37:07

标签: c++ observer-pattern observers

我正在忙于为遗留C ++应用程序添加通用观察器机制(使用Visual Studio 2010,但不使用.Net,因此.Net代表是不可能的。)

在设计中,我想尽可能地将特定于应用程序的部分与通用观察器机制分开。

实现观察者的最合乎逻辑的方式似乎是这样的:

class IDoThisObserver
   {
   public:
      void handlDoThis(int arg1, int arg2) = 0;
   };

对于每种类型的观察者(IDoThisObserver,IDoThatObserver,...),方法的参数(handleDoThis,handleDoThat)都不同。

存储观察者的一般方法仍然是这样的:

template<typename T>
class ObserverContainer
   {
   public:
      void addObserver (T &t) {m_observers.push_back(&t);}
   private:
      std::list<T*> m_observers;
   };

调用观察者不能一概而论,因为每个观察者类型的参数都不同。

另一种方法是将所有参数“打包”成一个参数,如下所示:

struct DoThisInfo
   {
   DoThisInfo (int arg1, int arg2) : m_arg1(arg1), m_arg2(arg2) {}
   int m_arg1;
   int m_arg2;
   };

然后定义一个更通用的观察者,如下所示:

template<typename T>
class IObserver
   {
   public:
      void notify(const T &t) = 0;
   };

这些观察者的集合将成为这个:

template<typename T>
class ObserverContainer
   {
   public:
      void addObserver (IObserver<T> &obs) {m_observers.push_back(&obs);}
   private:
      std::list<IObserver<T>*> m_observers;
   };

现在,可以将更多逻辑集中添加到此ObserverContainer,包括调用所有观察者。呼叫的“发起者”只需要创建并填写通知结构。

想要从多种观察者继承的类需要这样做:

class MyObserver : public IObserver<NotifyThis>, public IObserver<NotifyThat>
   {
   ...
   };

这些方法中的哪一种(具有多个显式参数或具有一个结构参数的观察者)似乎最好?这两种方法都有任何优点或缺点吗?

编辑:我对其他方法看起来更进一步,而Slot / Signal方法似乎是另一个很好的候选者。我应该知道Slot / Signal有什么重要的缺点吗?

5 个答案:

答案 0 :(得分:2)

为什么不这样做:

class IObserver {
    // whatever is in common
};

class IDoThisObserver : public IObserver
{
   public:
      void handlDoThis(int arg1, int arg2) = 0;
};

class IDoThatObserver : public IObserver
{
   public:
      void handlDoThat(double arg1) = 0;
};

然后你有:

class ObserverContainer
{
   public:
      void addObserver (IObserver* t) {m_observers.push_back(t);}
   private:
      std::list<IObserver*> m_observers;
};

答案 1 :(得分:1)

使用struct参数的设计肯定更好,因为它允许在ObserverContainer中编写通用代码。一般来说,用一个封装参数的对象替换冗长的参数列表是一个很好的设计实践,这是一个很好的例子。通过为notify方法创建更通用的抽象(使用结构定义notify作为获取大量“数据”的方法,而使用arg列表则定义一个方法需要两个数字)你允许自己编写使用该方法的通用代码,而不必关注传入的数据块的确切组成。

答案 2 :(得分:1)

我认为您的任何一种方法都不符合您的要求。然而,使用包含通过所有观察者的数据集的DataCarrier进行一点修改,其中每个观察者将知道要读取什么将成功。下面的示例代码可以清除它(注意我还没有编译)

 enum Type {
    NOTIFY_THIS,
    NOTIFY_THAT
 };

 struct Data {
 virtual Type getType() = 0;
 };

 struct NotifyThisData: public Data {
    NotifyThisData(int _a, int _b):a(_a), b(_b) { }
    int a,b;
    Type getType() { return NOTIFY_THIS; }
 };

 struct NotifyThatData: public Data {
    NotifyThatData(std::string _str):str(_str) { }
    std::string str;
    Type getType() { return NOTIFY_THAT; }
 };

 struct DataCarrier {
    std::vector<Data*> m_TypeData;  
 };

 class IObserver {
 public:
     virtual void handle(DataCarrier& data) = 0;
 };

 class NotifyThis: public virtual IObserver {
 public:
         virtual void handle(DataCarrier& data) {
                 vector<Data*>::iterator iter = find_if(data.m_TypeData.begin(), data.m_TypeData.end(), bind2nd(functor(), NOTIFY_THIS);
                 if (iter == data.m_TypeData.end())
                         return;
                 NotifyThisData* d = dynamic_cast<NotifyThisData*>(*iter);
                 std::cout << "NotifyThis a: " << d->a << " b: " << d->b << "\n";
         }
 };

 class NotifyThat: public virtual IObserver {
 public:
         virtual void handle(DataCarrier& data) {
                 vector<Data*>::iterator iter = find_if(data.m_TypeData.begin(), data.m_TypeData.end(), bind2nd(functor(),NOTIFY_THAT);
                 if (iter == data.m_TypeData.end())
                         return;            
                 NotifyThatData* d = dynamic_cast<NotifyThatData*>(*iter);
                 std::cout << "NotifyThat str: " << d->str << "\n";
         }
 };

 class ObserverContainer
    {
    public:
       void addObserver (IObserver* obs) {m_observers.push_back(obs);}
       void notify(DataCarrier& d) {
                 for (unsigned i=0; i < m_observers.size(); ++i) {
                         m_observers[i]->handle(d);
                 }
         }
    private:
       std::vector<IObserver*> m_observers;
    };

 class MyObserver: public NotifyThis, public NotifyThat {
 public:
         virtual void handle(DataCarrier& data) { std::cout << "In MyObserver Handle data\n"; }
 };

 int main() {
         ObserverContainer container;
         container.addObserver(new NotifyThis());
         container.addObserver(new NotifyThat());
         container.addObserver(new MyObserver());

         DataCarrier d;
         d.m_TypeData.push_back(new NotifyThisData(10, 20));
         d.m_TypeData.push_back(new NotifyThatData("test"));

    container.notify(d);
    return 0;
 }

这样,如果添加新结构,则只需要修改枚举。 你也可以使用boost :: shared_ptr来处理乱七八糟的指针。

答案 3 :(得分:1)

你看过Boost.Signals吗?比重新实现车轮更好。

至于参数:调用观察者/插槽应该在概念上与调用普通函数相同。大多数SignalSlots-Implementations允许多个参数,因此请使用它。请为不同的观察者类型使用不同的信号,然后不需要在Variants中传递数据。

Observer-Pattern / SignalSlots的两个缺点我见过:
1)仅通过查看来源,很难甚至无法理解程序流程 2)具有大量Observers / SignalSlots的重度动态程序可能会遇到“删除此”

除了一切之外,我更喜欢Observers / SignalSlots,而不是子类化,因此高度耦合。

答案 4 :(得分:0)

我不会得到正确的语法所以我只是列出声明来说明结构。可以使用通用观察者来期望参数既可以子类化为所需参数的特定形式,也可以是结构,包括观察者所需的所有基本参数的水平映射。然后ObserverContainer可以作为AbstractFactory运行,ObserverContainer的每个子类可以是DoThatObserverFactory和DoThisObserverFactory。工厂将构建一个观察者并为观察者分配一个配置,告诉它预期哪个参数。

class AbstractObserverFactory {...};
class DoThatObserverFactory : AbstractObserverFactory {...};
class DoThisObserverFactory : AbstractObserverFactory {...};
class ObserverParam {...};
class DoThatObserverParam : ObserverParam {...};
class DoThisObserverParam : ObserverParam {...};
class Observer;
class DoThisObserver : public Observer
{
   public:
      void handlDoThis(DoThisObserverParam);
};