私は現在、状態が変化した場合にクラスがオブザーバーを呼び出すことができるアプリケーションをリファクタリングしています。これは、オブザーバーが次の場合に呼び出されることを意味します。
- クラスが変更されたインスタンスのデータ
- クラスの新しいインスタンスが作成されます
- クラスのインスタンスが削除されます
私が心配するのはこの最後のケースです。
私のクラスが本だとします。オブザーバーは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");
}
}
この場合、リストにTolkienの本が含まれ、その後にCarl Saganの本が続く場合、次のイテレータ(itNext)が無効になるため、Tolkienのすべての本を削除するループがクラッシュする可能性があります。
示されている問題は他の状況でも発生する可能性がありますが、アプリケーションが簡単にクラッシュする可能性があるため、削除の問題が最も深刻です。
アプリケーションで、削除するすべてのインスタンスを最初に取得し、それらを2番目のコンテナーに入れてから、2番目のコンテナーをループしてインスタンスを削除することで問題を解決できますが、常にリスクがあるため、オブザーバーは、削除予定リストに既に含まれている他のインスタンスを明示的に削除します。この2番目のコピーも最新の状態に保つために、明示的なオブザーバーを配置する必要があります。
また、オブザーバーの呼び出し中に(直接または間接的に)コンテナーを反復処理する場合は常に、すべてのアプリケーションコードにリストのコピーを作成するように要求すると、アプリケーションコードの記述がはるかに困難になります。
このような問題を解決するために使用できる[デザイン]パターンはありますか?アプリケーション全体が共有ポインターを使用してインスタンスにアクセスすることを保証できないため、共有ポインターアプローチを使用しないことをお勧めします。