Cómo proteger la estructura de consistencia de los datos cuando se llama a la lógica externa a través de los observadores?

StackOverflow https://stackoverflow.com/questions/4082128

Pregunta

Actualmente estoy refactorización una aplicación en la que las clases pueden llamar a los observadores si su estado cambia. Esto significa que los observadores se les llama siempre:

  • los datos en una instancia de la clase cambia
  • nuevas instancias de la clase se crean
  • se eliminan instancias de la clase

Es este último caso, eso hace que me preocupe.

Supongamos que mi clase es libro. Los observadores se almacenan en una clase llamada BookManager (el BookManager también mantiene una lista de todos los libros). Esto significa que tenemos esto:

class Book
   {
   ...
   };

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

Si se elimina un libro (retirado de la lista y borra de la memoria), los observadores se llaman:

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

El problema es que no tengo ningún control sobre la lógica de los observadores. Los observadores pueden ser entregados por un plug-in, por el código que está escrito por los desarrolladores en el cliente.

Así que, aunque puedo escribir código como este (y asegurarse de conseguir el siguiente en la lista en caso de que se suprime la instancia):

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

Siempre existe la posibilidad de que un observador eliminación de otros libros de la lista, así:

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

En este caso, si la lista contiene un libro de Tolkien, seguido de un libro de Carl Sagan, el bucle que elimina todos los libros de Tolkien probablemente choque ya que el próximo iterador (itNext) ha de ser válida.

El problema ilustrado también pueden aparecer en otras situaciones, pero la eliminación de problemas es la más grave, ya que puede bloquear fácilmente la aplicación.

Me podría resolver el problema asegurándose de que en la aplicación por primera vez consigo todas las instancias que quiero borrar, ponerlos en un segundo recipiente, a continuación, un bucle sobre el segundo recipiente y eliminar los casos, pero ya que siempre existe la riesgo de que un observador elimina explícitamente otros casos que ya estaban en mi lista a-ser-suprimido, tengo que poner observadores explícitas para mantener esta segunda copia al día también.

También se requiere todo el código de aplicación para hacer copias de las listas siempre que lo deseen repetir un recipiente mientras llamando observadores (directa o indirectamente) hace que escribir el código de aplicación mucho más difícil.

¿Hay [diseño] patrones que se pueden utilizar para resolver este tipo de problema? enfoque triple compartida preferiblemente no ya que no puede garantizar que el conjunto de los usos de aplicaciones compartidas puntos para acceder a las instancias.

¿Fue útil?

Solución

El problema básico es que la colección de libros es modificado (sin conocimiento por la aplicación) mientras la aplicación está interactuando sobre la misma colección.

Dos formas de lidiar con que son:

  1. Introducir un mecanismo de bloqueo de la colección. La aplicación tiene un bloqueo en la recogida y mientras exista esa cerradura, no se permiten acciones que modifican la colección (libros de añadir / quitar). Si un observador necesidades para llevar a cabo una modificación durante ese tiempo, van a tener que recordarlo y llevar a cabo la modificación cuando se les notifique de la liberación de la cerradura.
  2. Use su propia clase de iterador, que puede hacer frente a la colección cambia por debajo de ella. Por ejemplo, al utilizar el patrón Observer para informar a todos los iteradores que un elemento está a punto de ser eliminado. Si eso invalidaría el iterador, se podría avanzar internamente siga siendo válida.

Si la eliminación de bucle es parte de BookManager, entonces podría ser reestructurado como esto:

  • bucle sobre la recogida y mover todos los elementos que se deben eliminar a un local de separada, 'borrado' colección. En esta etapa, sólo informar a los iteradores sobre los cambios en la colección (si la opción 2 anterior ha implementado).
  • Bucle sobre la colección 'borrado' e informar a los observadores acerca de cada eliminación. Si un observador intentos para borrar otros elementos, que no es ningún problema, siempre y cuando la eliminación de artículos que no existe no es un error grave.
  • Realizar la limpieza de la memoria en los elementos en el 'borrado' colección.

Algo similar se podría hacer para otras modificaciones de elementos múltiples en BookManager.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top