3

我正忙于向旧版 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>
   {
   ...
   };

这些方法中的哪一种(具有多个显式参数或具有一个结构参数的观察者)看起来最好?这两种方法有什么优点或缺点吗?

编辑:我进一步研究了替代方法,插槽/信号方法似乎是另一个不错的选择。我应该知道插槽/信号中有什么重要的缺点吗?

4

5 回答 5

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;
};
于 2010-08-30T15:43:16.660 回答
1

带有struct参数的设计肯定更好,因为它允许在ObserverContainer. 用封装参数的对象替换冗长的参数列表通常是一种很好的设计实践,这是一个很好的回报示例。通过为您的方法创建一个更通用的抽象notify(使用您定义的结构notify作为获取一大块“数据”的方法,而使用 arg 列表您定义一个使用两个数字的方法),您允许自己编写泛型使用该方法并且不必关心传入的数据块的确切组成的代码。

于 2010-08-30T16:26:41.517 回答
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 来处理指针的混乱。

于 2010-08-30T18:37:23.740 回答
1

您是否研究过 Boost.Signals?比重新实现轮子更好。

至于参数:调用观察者/插槽在概念上应该与调用普通函数相同。大多数 SignalSlots-Implementations 允许多个参数,所以使用它。并且请针对不同的观察者类型使用不同的信号,这样就不需要在 Variants 中传递数据。

我见过的 Observer-Pattern/SignalSlots 的两个缺点:
1) 仅查看源代码很难甚至无法理解程序流程。
2)具有大量观察者/信号槽的高度动态程序可能会遇到“删除这个”

除了继承之外,我更喜欢 Observers/SignalSlots,而不是子类化和高耦合。

于 2010-08-30T18:37:24.170 回答
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);
};
于 2010-08-30T17:58:40.377 回答