实现"观察者"模式的问题

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

非常有趣的问题.

试试这个:

  1. 更改remObserver以使该条目为空,而不是仅删除它(并使列表迭代器无效).
  2. 将您的notifyAll循环更改为:

    for(所有已登记的观察员){if(观察员)观察员 - > notify(); }

  3. 在notifyAll的末尾添加另一个循环,以从观察者列表中删除所有空条目

  • 我想如果你*真的*担心性能,你可以添加某种“脏”标志,这样最后一个循环就不必被激活,除非有东西要删除。但是,除非此循环存在经过验证和测量的性能问题,否则我不会打扰。过早的优化等等。 (2认同)

Tod*_*ner 7

就个人而言,我使用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)


Lee*_*Lee 6

一个男人去看医生说:"当我这样抬起手臂时,医生会伤到真正的坏人!" 医生说,"不要这样做."

最简单的解决方案是与您的团队合作,并告诉他们不要这样做.如果观察者"确实需要"自杀或所有观察者,则安排通知结束时的行动.或者,更好的是,更改remObserver函数以了解是否发生了通知进程,并在完成所有操作时排队删除.

  • @TED我完全同意这种模式不是一些永远不会因修改而被玷污的圣文.但是我经常发现,开发人员过于偏重地使用快速修复方法使事情变得复杂,并且通常不会退后一步并询问真正的问题是什么.可能是他们在未设计的情况下使用Observer模式. (2认同)

The*_*ish 5

这是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().但是只要在列表上运行的任何函数都适当地小心地检查空条目,那么这应该不是问题.