В настоящее время я занимаюсь рефакторингом приложения, в котором классы могут вызывать наблюдателей, если их состояние изменяется.Это означает, что наблюдатели вызываются всякий раз, когда:
- данные в экземпляре класса изменяются
- создаются новые экземпляры класса
- экземпляры класса удаляются
Именно этот последний случай заставляет меня волноваться.
Предположим, что мой класс - Книга.Наблюдатели хранятся в классе под названием 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) станет недействительным.
Иллюстрированная проблема может также появиться в других ситуациях, ноПроблема удаления является наиболее серьезной, поскольку она может легко вызвать сбой приложения.
Я мог бы решить проблему, убедившись, что в приложении я сначала получаю все экземпляры, которые хочу удалить, помещаю их в секундуконтейнер, затем переберите второй контейнер и удалите экземпляры, но всегда существует риск, что наблюдательзаконно удаляя другие экземпляры, которые уже были в моем списке для удаления, я должен поставить явных наблюдателей, чтобы также поддерживать эту вторую копию в актуальном состоянии.
Также требуется, чтобы весь код приложения делал копии списков всякий раз, когдаони хотят перебирать контейнер, в то время как вызов наблюдателей (прямо или косвенно) значительно усложняет написание кода приложения.
Существуют ли шаблоны [design], которые можно использовать для решения этой проблемы?Желательно, чтобы не было подхода с общими указателями, поскольку я не могу гарантировать, что все приложение использует совместно используемые указатели для доступа к экземплярам.