Как сохранить согласованность структуры данных при вызове внешней логики через наблюдателей? - PullRequest
3 голосов
/ 03 ноября 2010

В настоящее время я занимаюсь рефакторингом приложения, в котором классы могут вызывать наблюдателей, если их состояние изменяется.Это означает, что наблюдатели вызываются всякий раз, когда:

  • данные в экземпляре класса изменяются
  • создаются новые экземпляры класса
  • экземпляры класса удаляются

Именно этот последний случай заставляет меня волноваться.

Предположим, что мой класс - Книга.Наблюдатели хранятся в классе под названием 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], которые можно использовать для решения этой проблемы?Желательно, чтобы не было подхода с общими указателями, поскольку я не могу гарантировать, что все приложение использует совместно используемые указатели для доступа к экземплярам.

1 Ответ

1 голос
/ 03 ноября 2010

Основная проблема здесь заключается в том, что коллекция книг изменяется (без ведома приложения), пока приложение выполняет итерацию по той же коллекции.

Два способа справиться с этим:

  1. Ввести механизм блокировки на коллекции. Приложение блокирует коллекцию, и пока эта блокировка существует, никакие действия, изменяющие коллекцию (добавление / удаление книг), запрещены. Если наблюдателю необходимо выполнить модификацию в течение этого времени, он должен будет запомнить ее и выполнить модификацию, когда он получит уведомление о снятии блокировки.
  2. Используйте свой собственный класс итератора, который может иметь дело с изменением коллекции под ним. Например, используя шаблон Observer для информирования всех итераторов о том, что элемент собирается быть удаленным. Если это лишит законной силы итератор, он может продвинуться внутренне, чтобы остаться действительным.

Если цикл удаления является частью BookManager, его можно изменить следующим образом:

  • Зацикливание на коллекции и перемещение всех элементов, которые должны быть удалены, в отдельную локальную «удаленную» коллекцию. На этом этапе только сообщите итераторам (если вы реализовали опцию 2 выше) об изменениях в коллекции.
  • Обведите «удаленную» коллекцию и проинформируйте наблюдателей о каждом удалении. Если наблюдатель пытается удалить другие элементы, это не проблема, если удаление несуществующих элементов не является фатальной ошибкой.
  • Выполнить очистку памяти для элементов в «удаленной» коллекции.

Нечто подобное можно сделать и для других многоэлементных модификаций в BookManager.

...