参考文章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
会产生任何副作用吗?
答案 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_cast
从this
到T*
。
但是,您的代码非常接近称为奇怪重复模板模式的模式。为了使其完美适合,将派生类的类型传递给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;
}