2012-05-23 9 views
7

Ich habe eine Implementierung eines Zustandsmusters, wobei jeder Zustand Ereignisse verarbeitet, die er aus einer Ereigniswarteschlange erhält. Basis State Klasse hat daher eine rein virtuelle Methode void handleEvent(const Event*). Ereignisse erben die Basis Event Klasse, aber jedes Ereignis enthält seine Daten, die von einem anderen Typ sein können (z. B. int, string ... oder was auch immer). handleEvent muss den Laufzeittyp des empfangenen Ereignisses ermitteln und dann downcast ausführen, um Ereignisdaten zu extrahieren. Ereignisse werden dynamisch erzeugt und in einer Queue gespeichert (so erfolgt Upcasting findet hier statt ...).Wie vermeide ich Downcast?

Ich weiß, dass Downcasting ist ein Zeichen für ein schlechtes Design, aber ist es möglich, es in diesem Fall zu vermeiden? Ich denke an Visitor Muster, wo Basisklasse State virtuelle Handler für jedes Ereignis enthalten würde, aber dann wieder downcast muss in dem Stück Code stattfinden, die Ereignis aus einer Warteschlange entnimmt und es an den aktuellen Status übergibt. (Zumindest in diesem Fall wäre der große switch(eventID) nur an einer Stelle ...). Ist das Besucher-Muster der beste Weg, um Downcasting zu vermeiden? Hier

ist der Pseudo-Code (ich vorbei boost::shared_ptr in diesem Beispiel aber Downcasting geschieht sowieso):

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

     } 
    } 
} 

Antwort

6

Das typische Besuchermuster führt keine niedergeschlagenen, dank einer Doppel Versand Strategie:

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

Und die Implementierungen:

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

Der entscheidende Punkt hier ist, dass in v.handle(*this) der statische Typ *this ist EventBar const&, die die richtige virtual void handle(EventBar const&) = 0 Überlast in Visitor auswählt.

+1

Der Downcast wird im Aufruf von '' Event :: accept'' gemacht. Es wird über vtable in '' EventBar :: accept'' aufgelöst und '' this' wird dabei von '' Event'' nach '' EventBar'' umgewandelt. –

+0

Also gibt es keine anderen magischen Muster/Idiome, um Downcasting zu vermeiden? Meine einzige Sorge mit Besucher ist die Menge an repetitivem Code, der geschrieben werden muss. Aber es scheint der Preis dafür zu sein, dass wir nicht downcasts haben. Ich hätte dieses Problem nicht, wenn ich nur eine einzige Event-Klasse hätte und keine abgeleiteten Event-Klassen in der Warteschlange speichern müsste, aber das ist unvermeidlich. –

+0

@BojanKomazec: mindestens eine andere Möglichkeit ist die Verwendung von 'boost :: variant':' typedef boost :: variant Event; '. Durch das Entfernen der Hierarchie entfernen Sie die Downcasts :) –

2

Die Idee von Ereignissen besteht darin, detaillierte Objekte über eine verallgemeinerte (und agnostische) Schnittstelle weiterzuleiten. Downcast ist unvermeidlich und Teil des Designs. Schlecht oder gut, es ist strittig.

Das Besuchermuster verbirgt nur das Gussteil von Ihnen. Es wird immer noch hinter den Kulissen ausgeführt, Typen, die über die Adresse der virtuellen Methode aufgelöst werden.

Da Ihr Event bereits die ID hat, ist es nicht völlig agnostisch von dem Typ, so dass Casting absolut sicher ist. Hier sehen Sie den Typ persönlich, im Besuchermuster machen Sie den Compiler, der das erledigt.

"Was immer geht, muss sinken".

+0

Ich stimme der Verwendung von * safe * nicht zu. Es kann funktionieren, aber es ist sehr anfällig für Fehler, weil es manuell ist. Die Verwendung der Sprachmechanismen garantiert, dass a) keine Fehlsteuerungsunterdrückung auftritt und b) alle "Ziel" -Typen korrekt gehandhabt werden (Einschaltkennung könnte eine neue ID vergessen ...) –

+0

@MatthieuM. Je weniger Sie manuell erledigen müssen, desto weniger Spielraum für Fehler, stimme ich zu. Fragende Kompetenz eines Programmierers ist jedoch ein direkter Weg zu "Du bist dumm zu schreiben in C++" -Argument. C und C++ sind für Leute, die wissen, was sie tun. Das ist meine Annahme. Fehler in syntaktischer Zucker mit Funktionalität ist zu häufig. –

+0

* Je weniger man manuell machen muss, desto weniger Platz für Fehler * - das ist mein Credo :) Ich bevorzuge ein solches Design. Und mit dem Besucher sind keine EventID's nötig. –