Patrón de observador; sabiendo * que * cambió?

10

He creado dos clases abstractas de sujeto y observador que definen una interfaz de patrón de observador clásica. Derivo de ellos para implementar el patrón Observador. Un observador podría verse así:

void MyClass::Update(Subject *subject)
{
    if(subject == myService_)
    {
        DoSomething();
    }
    else if(subject == myOtherService_)
    {
        DoSomethingElse();
    }
}

Esto está bien y me dice quién cambió algo. Sin embargo, no me dice qué cambió. A veces esto está bien porque solo voy a consultar el Asunto para obtener los datos más recientes, pero otras veces necesito saber qué cambió exactamente en el Asunto. Noté que en Java tienen un método notifyObservers () y un método notifyObservers (Object arg) para presumiblemente especificar detalles sobre lo que cambió.

En mi caso, necesito saber si ocurrió una de un par de acciones diferentes sobre el tema y, si se trata de una acción en particular, conocer un número entero relacionado con esa acción.

Entonces mis preguntas son:

  1. ¿Cuál es la forma en C ++ de pasar un argumento genérico (como lo hace Java)?
  2. ¿Observer es incluso el mejor patrón? ¿Quizás algún tipo de sistema de eventos?

ACTUALIZAR

Encontré este artículo que habla sobre la plantilla del patrón Observador: Implementar un patrón Sujeto / Observador con plantillas . Esto me hizo preguntarme si podrías modelar un argumento.

Encontré esta pregunta de desbordamiento de pila que habla sobre la plantilla del argumento: Patrón de Observador de Sujetos basado en plantillas : ¿Debo usar static_cast o dynamic_cast ? Sin embargo, el OP parece tener un problema que nadie ha respondido.

La otra cosa que podría hacer es cambiar el método de actualización para tomar un objeto EventArg como en:

void MyClass::Update(Subject *subject, EventArg arg)
{
  ...

Y luego cree subclases de EventArg para datos de argumentos específicos, y luego supongo que lo devuelve a la subclase específica dentro del método de actualización.

ACTUALIZACIÓN 2

También encontré un artículo, Sobre la creación de un marco de trabajo asincrónico basado en c ++; parte 2, que trata de que el Sujeto comunique detalles sobre lo que cambió.

Ahora estoy considerando seriamente usar Boost.Signals . Usar mi propio patrón de observador tenía sentido cuando era simple, pero comenzar a complicar el tipo y el argumento. Y es posible que necesite la seguridad del hilo de Boost.Signals2.

ACTUALIZACIÓN 3

También encontré algunos artículos interesantes sobre el patrón de observación:

Observador generalizador por Herb Sutter

Implementación del patrón de observador en C ++ - Parte 1

Experiencias de implementación del patrón de diseño de observador (Parte 2)

Experiencias de implementación del patrón de diseño de observador (Parte 3)

Sin embargo, he cambiado mi implementación para usar Boost.Signals que, aunque posiblemente un poco hinchado para mis propósitos, funciona con éxito. Y probablemente cualquier preocupación de hinchazón o velocidad sea irrelevante.

Usuario
fuente
Las preguntas en el cuerpo realmente no parecen coincidir con su título. ¿Cuál es realmente el núcleo de la pregunta?
Nicole
@Renesis: Actualmente estoy usando el patrón Observador como en el ejemplo de código al comienzo de mi publicación. Para el código en el que estoy trabajando actualmente, resulta que necesito saber específicamente qué cambió para poder reaccionar en consecuencia. Mi implementación actual del patrón de observador (que es el estándar) no proporciona esta información. Mi pregunta es cómo obtener mejor información sobre lo que cambió.
Usuario
@Renesis: Aquí hay un hilo del foro que hace una pregunta similar a la mía: gamedev.net/topic/497105-observer-pattern
Usuario
Actualización de anuncio2: definitivamente apoyo el uso de Boost.Signals. Es mucho más conveniente que rodar el tuyo. También hay libsigc ++ si quería algo más liviano solo para esta tarea (Boost.Signals usa mucho código del resto de Boost); Sin embargo, no es seguro para subprocesos.
Jan Hudec
Con respecto a los problemas de velocidad: no sé específicamente qué tan rápido es Boost.Signals, pero eso es solo una preocupación cuando tienes una gran cantidad de eventos volando ...
Max

Respuestas:

4

Ya sea C ++ o JAVA, una Notificación al observador puede venir junto con la información de lo que ha cambiado. Los mismos métodos notifyObservers (Object arg) también se pueden usar en C ++ también.

En general, el problema seguirá siendo que podría haber múltiples sujetos enviados a uno o múltiples observadores y, por lo tanto, class argno se puede codificar.

Por lo general, la mejor manera de hacerlo es hacer arg en forma de mensajes / tokens genéricos que formen el mismo tipo de datos para varias clases pero los valores difieren para las diferentes clases observadas. Alternativamente, si todos esos valores de notificación se derivan de la clase en alguna clase basada que es común a todos.

Para el patrón de observador, es importante que el tipo de datos Arg no esté codificado entre el observador y el observador; de lo contrario, es un acoplamiento que dificulta la evolución.

EDITAR
Si desea que el observador no solo observe, sino que también necesite realizar muchas otras tareas en función de lo que ha cambiado en ese momento, también puede consultar el Patrón de visitante . En el patrón de visitante, el observador / visitante llama al objeto modificado y, por lo tanto, no solo puede saber cuál es la modificación, sino que también puede trabajar en él

Dipan Mehta
fuente
55
Si el observador interpreta el argumento, no es un acoplamiento, no importa cómo se oculta el tipo. De hecho, yo diría que es más difícil de evolucionar si pasa Object( void *, boost::anyo algo similarmente genérica) que si se pasa tipo específico, ya que con el tipo específico verá en tiempo de compilación que algo ha cambiado, mientras que con el tipo genérico se compilará y dejará de funcionar, porque el observador no podrá trabajar con los datos reales pasados.
Jan Hudec
@ JanHudec: Estoy de acuerdo con eso, pero ¿eso significa que crea un conjunto de subclases de observador / sujeto único y específico para cada argumento (es decir, para cada caso de uso)?
Usuario
@ JanHudec: también el acoplamiento es solo de una manera. El sujeto no tiene idea de los observadores. Sí, el observador sabe sobre el tema, pero ¿no es así como funciona el patrón de observación?
Usuario
1
@Usuario: Sí, hago una interfaz específica para cada sujeto y cada observador implementa las interfaces de los sujetos que necesita observar. Bueno, todos los lenguajes que uso tienen punteros de método enlazado en el lenguaje o marco (delegados de C #, C ++ 11 std::functionBoost boost::function, Gtk + GClosure, métodos enlazados de Python, etc.), así que solo defino métodos con las firmas apropiadas y le pido al sistema que cree el archivo real observador. El acoplamiento es de hecho solo una forma, el sujeto define la interfaz para los observadores, pero no tiene idea sobre su implementación.
Jan Hudec
0

Existen algunas formas de enviar un argumento de evento genérico "Java Like" en C ++.

1) Declare el argumento del evento como nulo * y vuélvalo a la clase correcta en el controlador de eventos.

2) Declare el argumento del evento como puntero / referencia a una nueva clase / interfaz como (en respuesta a su ejemplo)

class GenericEventArgument
{
  virtual bool didAction1Happen() = 0;
  virtual int getActionInteger() = 0;
};

Y haga que las clases de argumentos de eventos reales se deriven de esta clase.

En cuanto a su pregunta sobre un observador basado en plantilla, consulte

http://www.codeproject.com/Articles/3267/Implementing-a-Subject-Observer-pattern-with-templ

Gonen I
fuente
-1

No sé si este es el nombre canónico, pero de mis viejos días de Smalltalk recuerdo el término "aspecto" para identificar lo que ha cambiado en lo observable.

No es tan complejo como su idea de EventArg (y subclasificarla); simplemente pasa una cadena (incluso podría ser una constante entera) del observable al observador.

Además: solo hay dos métodos simples ( update(observable)yupdateAspect(observable, aspect)

Menos: el observador puede tener que pedirle al observable más información (es decir, su "número entero")

virtualnobi
fuente