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 Zoo
s 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;
}
精彩评论