3

我目前正在重构一个应用程序,如果它们的状态发生变化,类可以在其中调用观察者。这意味着无论何时都会调用观察者:

  • 类更改的实例中的数据
  • 创建类的新实例
  • 类的实例被删除

正是这最后一个案例让我担心。

假设我的班级是 Book。观察者存储在一个名为 BookManager 的类中(BookManager 还保存所有书籍的列表)。这意味着我们有这个:

class Book
   {
   ...
   };

class BookManager
   {
   private:
      std::list<Book *> m_books;
      std::list<IObserver *> m_observers;
   };

如果一本书被删除(从列表中删除并从内存中删除),则调用观察者:

void BookManager::removeBook (Book *book)
   {
   m_books.remove(book);
   for (auto it=m_observers.cbegin();it!=m_observers.cend();++it) (*it)->onRemove(book *);
   delete book;
   }

问题是我无法控制观察者的逻辑。观察者可以通过插件或客户开发人员编写的代码来交付。

因此,尽管我可以编写这样的代码(并且我确保在删除实例的情况下获得列表中的下一个):

auto itNext;
for (auto it=m_books.begin();it!=m_books.end();it=itNext)
  {
  itNext = it:
  ++itNext;
  Book *book = *it;
  if (book->getAuthor()==string("Tolkien"))
     {
     removeBook(book);
     }
  }

观察者总是有可能从列表中删除其他书籍:

void MyObserver::onRemove (Book *book)
   {
   if (book->getAuthor()==string("Tolkien"))
      {
      removeAllBooksFromAuthor("Carl Sagan");
      }
   }

在这种情况下,如果列表包含托尔金的书,然后是卡尔萨根的书,则删除所有托尔金书的循环可能会崩溃,因为下一个迭代器 (itNext) 已无效。

图示的问题也可能出现在其他情况下,但删除问题是最严重的,因为它很容易使应用程序崩溃。

我可以通过确保在应用程序中首先获取我想要删除的所有实例,将它们放入第二个容器中,然后遍历第二个容器并删除实例来解决这个问题,但由于总是存在观察者显式删除了已经在我的待删除列表中的其他实例,我必须放置显式观察者以使第二个副本也保持最新。

还要求所有应用程序代码在调用观察者(直接或间接)时在容器上迭代时复制列表,这使得编写应用程序代码变得更加困难。

是否有 [设计] 模式可用于解决此类问题?最好不要使用共享指针方法,因为我不能保证整个应用程序都使用共享指针来访问实例。

4

1 回答 1

1

这里的基本问题是 Book 集合在应用程序迭代同一个集合时被修改(应用程序不知道)。

两种处理方法是:

  1. 在集合上引入锁定机制。应用程序对集合进行锁定,只要该锁定存在,就不允许修改集合(添加/删除书籍)的操作。如果观察者在此期间需要执行修改,他们必须记住它并在收到锁释放通知时执行修改。
  2. 使用你自己的迭代器类,它可以处理它下面的集合变化。例如,通过使用观察者模式通知所有迭代器一个元素即将被删除。如果这会使迭代器无效,它可以在内部推进自身以保持有效。

如果 delete-loop 是 的一部分BookManager,那么它可以像这样重组:

  • 循环遍历集合并将所有应该删除的元素移动到单独的本地“已删除”集合中。在这个阶段,只通知迭代器(如果你实现了上面的选项 2)关于集合的变化。
  • 循环遍历“已删除”集合并通知观察者每次删除。如果观察者试图删除更多元素,只要删除不存在的项目不是致命错误,这没有问题。
  • 对“已删除”集合中的元素执行内存清理。

可以对BookManager.

于 2010-11-03T09:13:23.317 回答