2012-05-23 12 views
7

Tengo una implementación de Patrón de estado donde cada estado maneja los eventos que obtiene de una cola de eventos. Por lo tanto, la clase base State tiene un método virtual puro void handleEvent(const Event*). Los eventos heredan la clase base Event pero cada evento contiene sus datos que pueden ser de un tipo diferente (por ejemplo, int, cadena ... o lo que sea). handleEvent tiene que determinar el tipo de tiempo de ejecución del evento recibido y luego realizar downcast para extraer datos de eventos. Los eventos se crean dinámicamente y se almacenan en una cola (por lo que upcasting tiene lugar aquí ...).¿Cómo evitar el downcast?

Sé que el downcasting es un signo de un mal diseño, pero es posible evitarlo en este caso? Estoy pensando en Patrón de visitante donde el estado de la clase base contendría controladores virtuales para cada evento, pero luego se tendrá que realizar un downcast en el fragmento de código que dequeue el evento de una cola y lo pasa al estado actual. (Al menos en este caso grande switch(eventID) sería solo en un lugar ...). ¿El patrón de visitante es la mejor manera (práctica recomendada) para evitar la bajada?

Aquí es el pseudo-código (estoy pasando boost::shared_ptr en este ejemplo, pero downcasting pasa de todos modos):

enum EventID 
{ 
    EVENT_1, 
    EVENT_2, 
    ... 
}; 

class Event 
{ 
    EventID id; 
public: 
    Event(EventID id):id(id){} 
    EventID id() const {return id;} 
    virtual ~Event() = 0; 
}; 

class Event1 : public Event 
{ 
    int n; 
public: 
    Event1(int n):Event(EVENT_1), n(n){} 
    int getN() const {return n;} 
}; 

class Event2 : public Event 
{ 
    std::string s; 
public: 
    Event2(std::string s):Event(EVENT_2), s(s){} 
    std::string getS() const {return s;} 
}; 

typedef boost::shared_ptr<Event> EventPtr; 

class State 
{ 
    ... 
public: 
    ... 
    virtual ~State() = 0; 
    virtual void handleEvent(const EventPtr& pEvent) = 0; 
}; 

class StateA : public State 
{ 
    ... 
public: 
    void handleEvent(const EventPtr& pEvent) 
    { 
     switch(pEvent->id()) 
     { 
     case EVENT_1:   
      int n = boost::static_pointer_cast<Event1>(pEvent)->getN(); 
      ... 
      break; 
     case EVENT_2: 
      std::string s = boost::static_pointer_cast<Event2>(pEvent)->getS(); 
      ... 
      break; 
     ... 

     } 
    } 
} 

Respuesta

6

El patrón típico visitante realiza ninguna abatido, gracias a una estrategia de doble expedición:

// Visitor.hpp 
class EventBar; 
class EventFoo; 

class Visitor { 
public: 
    virtual void handle(EventBar const&) = 0; 
    virtual void handle(EventFoo const&) = 0; 
}; 

// Event.hpp 
class Visitor; 

class Event { 
public: 
    virtual void accept(Visitor&) const = 0; 
}; 

Y las implementaciones:

// EventBar.hpp 
#include <Event.hpp> 

class EventBar: public Event { 
public: 
    virtual void accept(Visitor& v); 
}; 

// EventBar.cpp 
#include <EventBar.hpp> 
#include <Visitor.hpp> 

void EventBar::accept(Visitor& v) { 
    v.handle(*this); 
} 

El punto clave aquí es que en v.handle(*this) el tipo estático de *this es EventBar const&, que selecciona la sobrecarga virtual void handle(EventBar const&) = 0 correcta en Visitor.

+1

El downcast se hace al llamar a '' Event :: accept''. Se resuelve a través de vtable con '' EventBar :: accept'' y '' this'' se convierte de '' Event'' a '' EventBar'' en el proceso. –

+0

Entonces, ¿no hay otros patrones/modismos mágicos para evitar el downcasting? Mi única preocupación con Visitor es la cantidad de código repetitivo que debe escribirse. Pero parece que es el precio de no tener downcasts. No tendría este problema si tuviera solo una clase de evento y no fuera necesario almacenar las clases de eventos derivados en la cola, pero eso es inevitable. –

+0

@BojanKomazec: al menos otra forma es usar 'boost :: variant':' typedef boost :: variant Event; '. Al eliminar la jerarquía, eliminas los downcasts :) –

2

La idea de los eventos es pasar objetos detallados a través de una interfaz generalizada (y agnóstica). Downcast es inevitable y parte del diseño. Malo o bueno, es discutible.

El patrón de visitante solo oculta el yeso de usted. Todavía se realiza detrás de escena, los tipos se resuelven a través de la dirección del método virtual.

Debido a que su Event ya tiene la identificación, no es completamente independiente del tipo, por lo que la fundición es perfectamente segura. Aquí está viendo el tipo personalmente, en el patrón de visitante está haciendo que el compilador se encargue de eso.

"Todo lo que sube debe bajar".

+0

No estoy de acuerdo con el uso de * safe *. Puede funcionar, pero es muy susceptible a errores porque es manual. El uso de los mecanismos de lenguaje garantiza que a) no se producirá un downcast equivocado yb) todos los tipos de "destino" se manejarán correctamente (al cambiar la ID se puede olvidar una nueva ID ...) –

+0

@MatthieuM. Mientras menos tenga que hacer manualmente, menos espacio habrá para el error, estoy de acuerdo. Sin embargo, la competencia de cuestionamiento de un programador es una forma directa de "Eres estúpido para escribir en C++" argumento. C y C++ son para personas que saben lo que están haciendo. Esa es mi suposición. Azúcar sintáctico erróneo con funcionalidad es muy común. –

+0

* Cuanto menos tenga que hacer manualmente, menor será el margen de error * - este es mi credo :) Prefiero tal diseño. Y con Visitor no hay necesidad de 'EventID's. –