开发者

Should I use dynamic cast in the subject observer pattern with templates

By referring to article Implementing a Subject/Observer pattern with templates

template <class T>
class Observer开发者_StackOverflow中文版
   {
   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;
   };

I was wondering instead of static_cast, shall I use dynamic_cast?

This is because if I am using static_cast, I will get compilation error in the following case.

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() {
    }
}

Is there any side effect of using dynamic_cast?


The best would surely be not to have to cast at all. You could change your notify() function so that it takes the right argument:

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

Now derived classes can pass the right object (this, if appropriate) without the base class needing to know the relation of derived classes to T.


Looking at your code as it is, that static_cast relies on the fact that whatever derives from Observer, will also derive from whatever it passes as a template argument. I think if this wouldn't hold, it would be caught at compile-time, because you couldn't static_cast from this to T*.

However, your code is very close to a pattern known as the Curiously Recurring Template Pattern. For it to fit perfectly, pass the derived class' type to Observer:

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

Now you don't need to derive from Observer's T anymore and whoever looks at it (hopefully) recognizes the pattern and understands the code more easily.


On a tangential line of reasoning, you can use existing libraries for this, like boost::signal that let you define an event and connect listeners (observers) to that event.

// 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
}

The advantage (and disadvantage) of this solution is that it loosens the coupling between the observer and the subject. This means that you cannot enforce that only Zoos can observe animals, or that the method used for observing has a concrete signature/name. This is at the same time an advantage if your Animal does not know or care who is observing:

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
}

Note that both the type and function name can be adapted by boost::bind. If a scientist works in two zoos, it could even be notified of which zoo the animal changing belongs to:

// [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;
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜