Frage

Ich bin Refactoring derzeit eine Anwendung, in der Klassen Beobachter nennen kann, wenn ihr Zustand ändert. Dies bedeutet, dass die Beobachter aufgerufen, wenn:

  • die Daten in einer Instanz der Klasse Änderungen
  • neue Instanzen der Klasse erstellt
  • Instanzen der Klasse gelöscht

Es ist dieser letzte Fall, der mich macht Sorgen.

Nehmen wir an, meine Klasse Buch. Die Beobachter in einer Klasse gespeichert werden genannt Bookmanager (der Bookmanager hält auch eine Liste aller Bücher). Das heißt, wir haben diese:

class Book
   {
   ...
   };

class BookManager
   {
   private:
      std::list<Book *> m_books;
      std::list<IObserver *> m_observers;
   };

Wenn ein Buch gelöscht (aus der Liste entfernt und aus dem Speicher gelöscht) werden die Beobachter genannt:

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

Das Problem ist, dass ich keine Kontrolle über die Logik in den Beobachter haben. Die Beobachter können durch ein Plugin geliefert werden, von Code, der von den Entwicklern beim Kunden geschrieben wird.

Also, auch wenn ich Code wie diesen schreiben kann (und ich sicherstellen, dass die nächsten in der Liste zu bekommen, falls die Instanz gelöscht wird):

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

Es gibt immer die Möglichkeit eines Beobachter Entfernen andere Bücher aus der Liste auch:

void MyObserver::onRemove (Book *book)
   {
   if (book->getAuthor()==string("Tolkien"))
      {
      removeAllBooksFromAuthor("Carl Sagan");
      }
   }

In diesem Fall, wenn die Liste ein Buch von Tolkien enthält, gefolgt von einem Buch von Carl Sagan, der Schleife, die alle Bücher von Tolkien löschen wahrscheinlich seit dem nächsten Iterator abstürzen (itNext) worden ist ungültig.

Das dargestellte Problem kann auch in anderen Situationen auftreten, aber das Lösch-Problem ist die schwerste, da sie leicht die Anwendung zum Absturz bringen können.

Ich konnte das Problem lösen, indem sichergestellt wird, dass bei der Anwendung ich zuerst alle Instanzen erhalten, dass ich löschen möchten, setzen Sie sie in einem zweiten Behälter, dann Schleife über den zweiten Behälter und die Instanzen löschen, aber da gibt es immer die Risiko, dass ein Beobachter explizit löscht andere Instanzen, die bereits waren auf meiner Liste zu-gelöscht werden, ich habe ausdrücklich Beobachter setzen diese zweite Kopie als auch auf dem Laufenden zu halten.

Auch erfordern alle Anwendungscode Kopien von Listen zu machen, wenn sie über einen Behälter zu durchlaufen wollen, während Beobachter fordern (direkt oder indirekt) macht die Anwendung das Schreiben von Code sehr viel schwieriger.

Gibt es [design] Muster, die verwendet werden können, um diese Art von Problem zu lösen? Vorzugsweise kein Shared-pointer Ansatz, da kann ich nicht garantieren, dass die gesamte Anwendung verwendet Shared-Zeiger auf die Instanzen zugreifen.

War es hilfreich?

Lösung

Das grundlegende Problem ist, dass die Buchauswahl (ohne Kenntnis von der Anwendung) geändert wird, während die Anwendung iterieren, dass gleiche Sammlung.

Zwei Möglichkeiten, das zu lösen sind:

  1. Führen Sie einen Verriegelungsmechanismus auf der Sammlung. Die Anwendung nimmt eine Sperre für die Sammlung und solange die Sperre existiert, keine Aktionen, die die Sammlung (Hinzufügen / Entfernen Bücher) sind erlaubt ändern. Wenn ein Beobachter braucht eine Änderung während dieser Zeit durchzuführen, werden sie es merken müssen und die Änderung durchführen, wenn sie von der Sperre Freilassung informiert werden.
  2. Verwenden Sie Ihre eigene Iterator-Klasse, die mit der Sammlung zu ändern darunter umgehen kann. Zum Beispiel durch die Beobachter-Muster mit allen Iteratoren zu informieren, dass ein Element über gelöscht werden soll. Wenn das der Iterator ungültig machen würde, könnte es sich intern vorantreiben bleiben gültig.

Wenn die Lösch-Loop-Teil BookManager ist, dann könnte es wie folgt neu strukturiert werden:

  • Schleife über die Sammlung und alle Elemente bewegen, die auf einem separaten, lokalen gelöscht werden soll, ‚gelöscht‘ Sammlung. Zu diesem Zeitpunkt informieren nur die Iteratoren über die Änderungen an der Sammlung (wenn Sie die Option 2 oben ausgeführt).
  • Schleife über die ‚gelöscht‘ Sammlung und informieren die Beobachter über jede Löschung. Wenn ein Beobachter versucht weitere Elemente zu löschen, das ist kein Problem, solange das Löschen nicht-vorhandene Elemente ist kein fataler Fehler.
  • Führen Speicherbereinigung auf die Elemente in der ‚gelöscht‘ Sammlung.

Etwas Ähnliches könnte auch für andere Multi-Element-Modifikationen in BookManager erfolgen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top