3

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

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

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

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

Иллюстрированная проблема может также возникать в других ситуациях, но проблема с удалением является самой серьезной, так как она может легко свернуть приложение.

Я мог бы решить эту проблему, убедившись, что в приложении я сначала получаю все экземпляры, которые хочу удалить, поместите их во второй контейнер, затем переверните второй контейнер и удалите экземпляры, но поскольку всегда есть риск того, что наблюдатель явно удалит другие экземпляры, которые уже были в моем списке для удаления, я должен предоставить явным наблюдателям возможность обновить эту вторую копию.

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

Есть ли [дизайн] шаблонов, которые могут быть использованы для решения этой проблемы? Не рекомендуется использовать метод общего указателя, поскольку я не могу гарантировать, что все приложение использует общие указатели для доступа к экземплярам.

+3

Похоже, ваша проблема глубже: ваши клиенты имеют прямой доступ к итераторам коллекции, и в то же время вы хотите, чтобы в коллекции было сложное поведение, не сохраняющее итераторы. Я бы изолировал API для операций мутаторов, явно заявляя, что любые итераторы, удерживаемые клиентами, становятся недействительными. –

ответ

1

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

Два способа решения, которые являются:

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

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

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

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