Sad*_*ido 32 c++ design-patterns observer-pattern
在使用C++和STL实现Observer模式时,我遇到了一个有趣的问题.考虑这个经典的例子:
class Observer {
public:
virtual void notify() = 0;
};
class Subject {
public:
void addObserver( Observer* );
void remObserver( Observer* );
private:
void notifyAll();
};
void Subject::notifyAll() {
for (all registered observers) { observer->notify(); }
}
Run Code Online (Sandbox Code Playgroud)
这个例子可以在每本关于设计模式的书中找到.不幸的是,现实系统更复杂,所以这是第一个问题:一些观察者决定在收到通知时将其他观察者添加到主题.这使我使用的"for"循环和所有迭代器无效.解决方案相当简单 - 我为已注册的观察者列表创建快照并迭代快照.添加新观察者不会使快照无效,所以一切似乎都可以.但是这里出现了另一个问题:观察者决定在收到通知后自行销毁.更糟糕的是,一个观察者可以决定销毁所有其他观察者(它们是从脚本控制的),这会使队列和快照无效.我发现自己在重新分配的指针上进行迭代.
我的问题是,当观察者互相残杀时,我应该如何处理这些情况呢?有没有现成的模式?我一直认为"观察者"是世界上最简单的设计模式,但现在似乎并不容易正确实现它......
谢谢大家,感谢您的关注.让我们有一个决定摘要:
[1]"不要这样做"对不起,但这是必须的.观察者从脚本控制并被垃圾收集.我无法控制垃圾收集以防止它们的取消分配;
[2]"使用boost :: signal"最有希望的决定,但我不能在项目上引入提升,这样的决定必须由项目负责人做出(我们在Playstation下编写);
[3]"使用shared__ptr"这将阻止观察者解除分配.一些子系统可能依赖于内存池清理,所以我认为我不能使用shared_ptr.
[4]"推迟观察者释放"队列观察员在通知时删除,然后使用第二个周期删除它们.不幸的是,我无法阻止重新分配,所以我使用了某种"适配器"包装观察者的技巧,实际上保留了"适配器"列表.在析构函数中,观察者从他们的适配器取消分配,然后我采取第二个周期来销毁空适配器.
ps是没关系,我编辑我的问题来总结所有的帖子?我是StackOverflow上的菜鸟......
T.E*_*.D. 14
非常有趣的问题.
试试这个:
将您的notifyAll循环更改为:
for(所有已登记的观察员){if(观察员)观察员 - > notify(); }
在notifyAll的末尾添加另一个循环,以从观察者列表中删除所有空条目
就个人而言,我使用boost :: signals来实现我的观察者; 我必须检查,但我相信它处理上述情况(编辑:找到它,请参阅"何时可以断开连接").它简化了您的实现,并且它不依赖于创建自定义类:
class Subject {
public:
boost::signals::connection addObserver( const boost::function<void ()>& func )
{ return sig.connect(func); }
private:
boost::signal<void ()> sig;
void notifyAll() { sig(); }
};
void some_func() { /* impl */ }
int main() {
Subject foo;
boost::signals::connection c = foo.addObserver(boost::bind(&some_func));
c.disconnect(); // remove yourself.
}
Run Code Online (Sandbox Code Playgroud)
一个男人去看医生说:"当我这样抬起手臂时,医生会伤到真正的坏人!" 医生说,"不要这样做."
最简单的解决方案是与您的团队合作,并告诉他们不要这样做.如果观察者"确实需要"自杀或所有观察者,则安排通知结束时的行动.或者,更好的是,更改remObserver函数以了解是否发生了通知进程,并在完成所有操作时排队删除.
这是TED已经提出的想法的变化.
只要remObserver可以使条目为空而不是立即删除它,那么您可以将notifyAll实现为:
void Subject::notifyAll()
{
list<Observer*>::iterator i = m_Observers.begin();
while(i != m_Observers.end())
{
Observer* observer = *i;
if(observer)
{
observer->notify();
++i;
}
else
{
i = m_Observers.erase(i);
}
}
}
Run Code Online (Sandbox Code Playgroud)
这避免了第二次清理循环的需要.但是,它确实意味着如果某个特定的notify()调用触发了自身或位于列表中较早位置的观察者的移除,那么list元素的实际删除将推迟到下一个notifyAll().但是只要在列表上运行的任何函数都适当地小心地检查空条目,那么这应该不是问题.