我应该使用模板在主题观察者模式中使用动态强制转换

时间:2010-10-20 07:31:16

标签: c++ design-patterns

参考文章Implementing a Subject/Observer pattern with templates

template <class T>
class Observer
   {
   public:
      Observer() {}
      virtual ~Observer() {}
      virtual void update(T *subject)= 0;
   };

template <class T>
class Subject
   {
   public:
      Subject() {}
      virtual ~Subject() {}
      void attach (Observer<T> &observer)
         {
         m_observers.push_back(&observer);
         }
      void notify ()
         {
         std::vector<Observer<T> *>::iterator it;
         for (it=m_observers.begin();it!=m_observers.end();it++) 
              (*it)->update(static_cast<T *>(this));
         }
   private:
      std::vector<Observer<T> *> m_observers;
   };

我想知道而不是static_cast,我应该使用dynamic_cast吗?

这是因为如果我使用static_cast,我将在以下情况下遇到编译错误。

class Zoo : public Observer<Animal> {
public:
    Zoo() {
        animal = new Bird();
        animal->attach(this);
    }

    virtual ~Zoo() {
    delete animal;
    }

    virtual void update(Animal* subject) {
    }

    Animal* animal;
}

// If using static_cast, compilation error will happen here.
class Bird : public Animal, public Subject<Animal> {
public:
    virtual ~Bird() {
    }
}

使用dynamic_cast会产生任何副作用吗?

2 个答案:

答案 0 :(得分:4)

最好的当然不是必须施展。您可以更改notify()函数,使其采用正确的参数:

  void notify (T* obj)
     {
     std::vector<Observer<T> *>::iterator it;
     for (it=m_observers.begin();it!=m_observers.end();it++) 
          (*it)->update(obj);
     }

现在,派生类可以传递正确的对象(this,如果适用),而基类不需要知道派生类与T的关系。


按照原样查看代码,static_cast依赖于这样一个事实,即从Observer派生的任何内容也将从它作为模板参数传递的任何内容中派生出来。我认为如果这不成立,它将在编译时被捕获,因为你不能static_castthisT*

但是,您的代码非常接近称为奇怪重复模板模式的模式。为了使其完美适合,将派生类的类型传递给Observer

class Bird : public Subject<Bird> // note the template argument

现在您不再需要从Observer的{​​{1}}派生出来,无论谁看到它(希望如此)都能识别模式并更容易理解代码。

答案 1 :(得分:2)

在一个相似的推理线上,您可以使用现有的库,例如boost::signal,您可以定义事件并连接侦听器(观察者)那件事。

// Forgive the lack of encapsulation and const-correctness to keep the example simple:
struct Animal {
   boost::signal< void ( Animal& )> signal_update;
   std::string name;
};
class Bird : public Animal {
public:
   void rename( std::string const & n ) { 
      name = n;
      signal_update(*this);
   }
};
class Zoo
{
public:
   Zoo() : bird() {
      bird.signal_update.connect( boost::bind( &Zoo::an_animal_changed, this, _1 ) );
   }
   void an_animal_changed( Animal & a ) {
      std::cout << "New name is " << a.name << std::endl;
   }
   Bird bird;
};
int main() {
   Zoo zoo;
   zoo.bird.rename( "Tweety" ); // New name is Tweety
}

该解决方案的优点(和缺点)是它松开了观察者和受试者之间的耦合。这意味着您无法强制只有Zoo可以观察动物,或者用于观察的方法具有具体的签名/名称。如果您的Animal不知道或不关心谁在观察,这同时也是一个优势:

class Scientist {
public:
   Scientist( Zoo & zoo )
   {
      zoo.bird.signal_update.connect( boost::bind( &Scientist::study, this, _1 ) );
   }
   void study( Animal & a ) {
      std::cout << "Interesting specimen this " << a.name << std::endl;
   }
};
int main() {
   Zoo zoo;
   Scientist pete(zoo);
   zoo.bird.rename( "Tweety" ); // New name is: Tweety
                                // Interesting specimen this Tweety
}

请注意,boost::bind可以调整类型和函数名称。如果一个科学家在两个动物园工作,甚至可以通知动物改变属于哪个动物园:

// [skipped]: added a name to the zoo
void Scientist::work_at( Zoo & zoo ) {
   zoo.bird.signal_update.connect( boost::bind( &Scientist::study, this, _1, zoo.name ) );
}
// updated signature:
void Scientist::study( Animal & a, std::string const & zoo_name )
{
   std::cout << "Interesting animal " << a.name << " in zoo " << zoo_name << std::endl;
}