Pregunta Cómo implementar el patrón Observer en C ++ [cerrado]


Tengo una clase de animación. Necesito tener algunos observadores para Play, Pause y Stop eventos en la animación. Encontré 2 soluciones para este problema, pero no sé qué elegir.

  1. Utilice boost :: señales o algo similar y registre devoluciones de llamada para cada evento

  2. Haga una interfaz simple con 3 funciones virtuales puras (OnPlay(), OnPause(), OnStop()) y pasar a los objetos de la clase Animación que implementan esta interfaz.

Hay ventajas y desventajas para cada método. Trataré de enumerar los que encontré hasta ahora:

Ventajas para 1.

  • Puedo usar cualquier función miembro / función libre como devolución de llamada
  • No tengo que implementar las 3 funciones si no me importan todas
  • El mismo objeto se puede usar como observador para múltiples animaciones sin pasar parámetros adicionales de la clase de Animación

Desventajas para 1.

  • Tengo que crear un objeto llamable para cada devolución de llamada
  • Si deseo agregar más tarde un nuevo evento, será difícil encontrar los lugares donde se usó (el compilador no puede obligarme a implementar o ignorar el nuevo evento).
  • De alguna manera, la sintaxis es extraña (tengo que usar std :: bind / boost :: bind en todas partes).

Ventajas para 2.

  • Construcción fácil de entender
  • Si agrego un nuevo evento en la clase de interfaz Animación / Observador, el compilador me obligará a implementar (vacíe quizás) la nueva función.

Desventajas para 2.

  • Tengo que implementar (vacíe quizás) 3 funciones, incluso si voy a usar solo una
  • El mismo objeto no se puede usar como observador para diferentes animaciones sin enviar algún parámetro extra de la animación (ID o algo así).
  • Las funciones gratuitas no pueden ser utilizadas.

¿Puede por favor aconsejarme qué usar? Según su experiencia, ¿qué es mejor para este problema: la libertad de un primer enfoque o un código claro y fácil de entender del segundo? ¿Puede darme otras ventajas / desventajas para ambos métodos u otra solución?


9
2017-07-29 12:46


origen


Respuestas:


En primer lugar, sería útil saber si el "enlace" se conoce en tiempo de compilación o no. Si es así, te sugiero que estudies las clases de políticas.

Aparte de eso, elegiría una combinación de las dos soluciones, es decir, usaría el enfoque de interfaz e implementaría una interfaz que actúa como un retransmisor de señales / funciones libres. De esta forma puede tener comportamientos predeterminados, puede agregar objetos personalizados que implementen toda la interfaz y, básicamente, las ventajas de los dos enfoques, así como también mucha flexibilidad.

Aquí hay un ejemplo básico del enfoque propuesto, espero que sea de ayuda.

#include <functional>

using namespace std;

template <class ObserverPolicy>
class Animation : public ObserverPolicy{

};

class MonolithicObserver{
    public:
    void play(){
        state = playing;
    }
    void pause(){
        if(playing == state)
            state = stopped;
    }
    void stop(){
        state = stopped;
    }
    private:
    enum {playing, paused, stopped} state;
};

struct doNothing{
    static void play(){}
    static void pause(){}
    static void stop(){}
};

struct throwException{
    class noPlay{};
    class noPause{};
    class noStop{};
    static void play(){
        throw noPlay();
    }
    static void pause(){
        throw noPause();
    }
    static void stop(){
        throw noStop();
    }
};

template <class DefaultPolicy = doNothing>
class FreeFunctionObserver{
    public:
    void play(){
        if(playHandle)
            playHandle();
        else
            DefaultPolicy::play();
    }
    void pause(){
        if(pauseHandle)
            pauseHandle();
        else
            DefaultPolicy::pause();
    }
    void stop(){
        if(stopHandle)
            stopHandle();
        else
            DefaultPolicy::stop();
    }
    void setPlayHandle(std::function<void(void)> p){
        playHandle = p;
    }
    void setPauseHandle(std::function<void(void)> p){
        pauseHandle = p;
    }
    void setStopHandle(std::function<void(void)> p){
        stopHandle = p;
    }
    private:
    std::function<void(void)> playHandle;
    std::function<void(void)> pauseHandle;
    std::function<void(void)> stopHandle;
};

void play(){}
void pause(){}
void stop(){}

int main(){
    Animation<FreeFunctionObserver<> > affo;
    affo.setPlayHandle(play);
    affo.setPauseHandle(pause);
    affo.setStopHandle(stop);
    affo.play();
    affo.pause();
    affo.stop();

    Animation<FreeFunctionObserver<throwException> > affot;
    try{
        affot.play();
    }
    catch(throwException::noPlay&){}

    Animation<MonolithicObserver> amo;
    amo.play();
    amo.pause();
    amo.stop();
}

que puedes probar aquí. Este ejemplo, en particular, utiliza una clase de política (por lo tanto, no se define "formalmente" la interfaz, y se puede "enriquecer" la interfaz, como se hizo con setPlayHandle). Sin embargo, también puede hacer algo similar con el enlace en tiempo de ejecución.


3
2017-07-29 12:54



Para todos menos el más simple de los ejemplos de juguetes, Boost.Signals2 sería la solución superior en mi opinión. Está bien diseñado, bien probado y bien documentado. Reinventar la rueda es bueno para los ejercicios de tipo tarea, pero no para el código de producción. P.ej. hacer que su propio observador sea seguro para la ejecución de subprocesos no es trivial para ser correcto y eficiente.

Hablando específicamente de tus desventajas enumeradas

  • puedes escribir C ++ 11 lambdas en lugar de objetos de función nombrados o usando boost::bind sintaxis (que de todos modos no es realmente complicada para la mayoría de los usos)
  • No entiendo muy bien tu punto acerca de los eventos no utilizados. Puedes hacer bastante avanzado gestión de la conexión para consultar y desconectar señales de las ranuras.

TL; DR: familiarícese con Boost.Signals2


1
2017-07-29 14:29



Creo que puedes usar ambos :) pero depende de las necesidades. Tengo un código donde uso ambos patrones. Hay muchas funciones llamadas OnSomething () (onMouseButton (), onKey (), onDragStart () etc), pero también hay callbacks. Cuando necesito implementar algún comportamiento, pero para toda la clase de objetos, uso el enfoque onSomething (). Pero si tengo un grupo de objetos de la misma clase, pero solo una parte de ellos necesita funcionalidad extendida, la devolución de llamada es una manera perfecta de hacerlo.

En la implementación, se hace así: hay algún código de envío que intenta usar el método onSomething () (que devuelve bool), si el resultado es falso, entonces hay que verificar si se define una devolución de llamada, si es así, se ejecuta.


0
2017-07-29 12:59