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");
      }
   }

この場合、リストにTolkienの本が含まれ、その後にCarl Saganの本が続く場合、次のイテレータ(itNext)が無効になるため、Tolkienのすべての本を削除するループがクラッシュする可能性があります。

示されている問題は他の状況でも発生する可能性がありますが、アプリケーションが簡単にクラッシュする可能性があるため、削除の問題が最も深刻です。

アプリケーションで、削除するすべてのインスタンスを最初に取得し、それらを2番目のコンテナーに入れてから、2番目のコンテナーをループしてインスタンスを削除することで問題を解決できますが、常にリスクがあるため、オブザーバーは、削除予定リストに既に含まれている他のインスタンスを明示的に削除します。この2番目のコピーも最新の状態に保つために、明示的なオブザーバーを配置する必要があります。

また、オブザーバーの呼び出し中に(直接または間接的に)コンテナーを反復処理する場合は常に、すべてのアプリケーションコードにリストのコピーを作成するように要求すると、アプリケーションコードの記述がはるかに困難になります。

このような問題を解決するために使用できる[デザイン]パターンはありますか?アプリケーション全体が共有ポインターを使用してインスタンスにアクセスすることを保証できないため、共有ポインターアプローチを使用しないことをお勧めします。

4

1 に答える 1

1

ここでの基本的な問題は、アプリケーションが同じコレクションを反復処理しているときに、Bookコレクションが(アプリケーションによる知識なしに)変更されることです。

これに対処する2つの方法は次のとおりです。

  1. コレクションにロックメカニズムを導入します。アプリケーションはコレクションをロックし、そのロックが存在する限り、コレクションを変更するアクション(ブックの追加/削除)は許可されません。その間にオブザーバーが変更を実行する必要がある場合、オブザーバーはそれを記憶し、ロックの解放が通知されたときに変更を実行する必要があります。
  2. 独自のイテレータクラスを使用して、その下で変化するコレクションを処理できます。たとえば、オブザーバーパターンを使用して、要素が削除されようとしていることをすべてのイテレーターに通知します。それがイテレータを無効にする場合、それは有効なままであるために内部的にそれ自体を進めることができます。

削除ループがの一部である場合は、次のBookManagerように再構築できます。

  • コレクションをループして、削除する必要のあるすべての要素を別のローカルの「削除済み」コレクションに移動します。この段階では、コレクションへの変更についてのみイテレーターに通知します(上記のオプション2を実装した場合)。
  • 'deleted'コレクションをループし、各削除についてオブザーバーに通知します。オブザーバーがさらに要素を削除しようとしても、存在しないアイテムを削除しても致命的なエラーでない限り、問題はありません。
  • 'deleted'コレクション内の要素に対してメモリクリーンアップを実行します。

の他の複数要素の変更についても、同様のことができますBookManager

于 2010-11-03T09:13:23.317 に答える