代码之家  ›  专栏  ›  技术社区  ›  Patrick

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

  •  2
  • Patrick  · 技术社区  · 14 年前

    我正忙着向传统C++应用程序添加通用的观察机制(使用VisualStudio 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;
       };
    

    现在,更多的逻辑可以集中添加到这个observer容器中,包括调用所有的observer。调用的“发起方”只需要创建并填充通知结构。

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

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

    以下哪种方法(具有多个显式参数或一个结构参数的观察者)看起来是最好的?这两种方法各有利弊吗?

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

    5 回复  |  直到 14 年前
        1
  •  1
  •   bshields    14 年前

    具有 struct ObserverContainer . 用封装参数的对象替换长参数列表通常是一种很好的设计实践,这是一个很好的例子。通过为您的 notify 方法(使用您正在定义的结构 通知 作为一个获取“数据”块的方法,而使用arg list定义一个获取两个数字的方法),您可以编写使用该方法的通用代码,而不必关心传入的数据块的确切组成。

        2
  •  2
  •   Kornel Kisielewicz    14 年前

    为什么不直接做:

    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;
    };
    
        3
  •  1
  •   Markus Kull    14 年前

    你查过了吗助推。信号?总比重装轮子好。

    至于参数:从概念上来说,调用一个观察者/槽应该和调用一个普通函数一样。大多数SignalSlots实现都允许多个参数,所以请使用它。请为不同的观察者类型使用不同的信号,这样就不需要在变量中传递数据。

    观察者模式/信号槽的两个缺点:
    1) 只看源代码很难理解程序流,甚至不可能理解程序流。
    2) 具有大量观察者/信号槽的高度动态程序可能会遇到“删除此项”

    除此之外,我更喜欢观察者/信号槽,而不是子类化和高耦合。

        4
  •  1
  •   aeh    14 年前

     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\u ptr来处理指针的混乱。

        5
  •  0
  •   Aaron    14 年前

    我没有正确的语法,所以我只列出声明来说明结构。泛型观察者可以期望一个参数,该参数要么是子类化为所需参数的特定形式,要么是包含观察者所需的所有基本参数的水平映射的结构。然后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);
    };