2010-05-04 8 views
2

J'essaie de remplacer des énumérations simples par des classes de types .. c'est-à-dire, une classe dérivée d'une base pour chaque type. Ainsi, par exemple au lieu de:Comment convertir élégamment switch + enum avec polymorphisme

enum E_BASE { EB_ALPHA, EB_BRAVO }; 
E_BASE message = someMessage(); 
switch (message) 
{ 
    case EB_ALPHA: applyAlpha(); 
    case EB_BRAVO: applyBravo(); 
} 

Je veux faire:

Base* message = someMessage(); 
message->apply(this); // use polymorphism to determine what function to call. 

Je l'ai vu beaucoup de façons de le faire, qui tous semblent moins élégante même alors l'instruction switch de base. En utilisant dyanimc_cast, héritant d'une classe messageHandler qui doit être mise à jour chaque fois qu'un nouveau message est ajouté, en utilisant un conteneur de pointeurs de fonction, tout semble aller à l'encontre du but de simplifier le code en remplaçant les commutateurs par du polymorphisme.

C'est aussi proche que je peux obtenir: (j'utiliser des modèles pour éviter héritant d'un omniscient interface gestionnaire)

class Base 
{ 
public: 
    template<typename T> virtual void apply(T* sandbox) = 0; 
}; 

class Alpha : public Base 
{ 
public: 
    template<typename T> virtual void apply(T* sandbox) 
    { 
     sandbox->applyAlpha(); 
    } 
}; 

class Bravo : public Base 
{ 
public: 
    template<typename T> virtual void apply(T* sandbox) 
    { 
     sandbox->applyBravo(); 
    } 
}; 

class Sandbox 
{ 
public: 
    void run() 
    { 
     Base* alpha = new Alpha; 
     Base* bravo = new Bravo; 

     alpha->apply(this); 
     bravo->apply(this); 

     delete alpha; 
     delete bravo; 
    } 
    void applyAlpha() { 
     // cout << "Applying alpha\n"; 
    } 

    void applyBravo() { 
     // cout << "Applying bravo\n"; 
    } 
}; 

De toute évidence, cela ne compile pas, mais je suis en espérant qu'il obtient mon problème à travers.

+0

Vous ne montrant que la consommation de messages. Comment sont-ils produits? Est-ce qu'il y a un besoin de sandbox-> applyBravo (bravo) ou autre? Plus d'info aiderait. – Arkadiy

+0

Passer un paramètre serait sympa - 'classe EatPotatoes: public Message {public: int combien};' qui aboutirait à appeler 'sandbox-> eatPotatoes (5);' si howMany égalait 5, par exemple. – Kyle

Répondre

3

Eh bien, après avoir donné pour dynamic_cast et l'héritage multiple, je suis venu avec ce grâce à Anthony Williams et jogear.net

class HandlerBase 
{ 
public: 
    virtual ~HandlerBase() {} 
}; 

template<typename T> class Handler : public virtual HandlerBase 
{ 
public: 
    virtual void process(const T&)=0; 
}; 

class MessageBase 
{ 
public: 
    virtual void dispatch(HandlerBase* handler) = 0; 

    template<typename MessageType> 
    void dynamicDispatch(HandlerBase* handler, MessageType* self) 
    { 
     dynamic_cast<Handler<MessageType>&>(*handler).process(*self); 
    } 
}; 

template<typename MessageType> class Message : public MessageBase 
{ 
    virtual void dispatch(HandlerBase* handler) 
    { 
     dynamicDispatch(handler, static_cast<MessageType*>(this)); 
    } 
}; 

class AlphaMessage : public Message<AlphaMessage> 
{ 
}; 

class BravoMessage : public Message<BravoMessage> 
{ 
}; 

class Sandbox : public Handler<AlphaMessage>, public Handler<BravoMessage> 
{ 
public: 
    void run() 
    { 
     MessageBase* alpha = new AlphaMessage; 
     MessageBase* bravo = new BravoMessage; 

     alpha->dispatch(this); 
     bravo->dispatch(this); 

     delete alpha; 
     delete bravo; 
    } 
    virtual void process(const AlphaMessage&) { 
     // cout << "Applying alpha\n"; 
    } 

    virtual void process(const BravoMessage&) { 
     // cout << "Applying bravo\n"; 
    } 
}; 


int main() 
{ 
    Sandbox().run(); 
    return 0; 
} 
+2

Le polymorphisme ne nécessite pas d'allocation dynamique. Vous pourriez parfaitement avoir 'alpha' et' bravo' sur la pile dans votre méthode 'Sandbox :: run';) Je pense aussi que vous avez manqué quelques subtilités au niveau de l'accès (le' dynamicDispatch' est protégé) dans l'article original et bien sûr le problème flagrant (à mes yeux) de passer des pointeurs quand vous devriez passer des références (vous ne testez pas pour la nullité). Bel article quand même, merci pour le lien. –

2

On dirait que vous essayez de trouver une sorte de système à double répartition. Regardez dans le modèle de visiteur ou d'autres systèmes à répartition multiple.

+0

Double Dispatch ne serait utile que si nous devons appeler sandbox-> appliquer (bravo) et sandbox-> appliquer (alpha), je pense – Arkadiy

+0

Y at-il une version de Double Dispatch qui ne nécessite pas une classe d'interface tout savoir ou dynamic_cast ? – Kyle

2

Vos classes Bravo et Alpha sont en réalité des fermetures ... Dommage que C++ ne les supporte pas directement.

Vous pouvez utiliser un pointeur de membre pour ce faire:

typedef void (Sandbox::*SandboxMethod)(); 

struct BrAlpha { 
    BrAlpha(SandboxMethod method) : method(method){} 
    void apply(Sandbox sb){sb->*method();} 
}; 

BrAlpha alpha(&Sandbox::applyAlpha); 
BrAlpha bravo(&Sandbox::applyBravo); 

(syntaxe peut ne pas être exacte, mais vous savez chapeau que je veux dire)

0

Je n'ai pas nécessairement une réponse pour votre conception problème de modèle (bien que Modern C++ Design a beaucoup à dire à ce sujet), mais je tiens à répondre à votre commentaire de commutation vs héritage.

Le problème avec cette instruction swtich simple est la maintenabilité. Si cette instruction switch était à 1 emplacement, alors il est probablement à peu près le même type de frappe pour créer des classes et hériter, mais cette instruction switch est toujours une bombe à retardement attendant l'ajout d'un autre état sans ajouter de casse. Si vous affirmez la valeur par défaut :, vous l'attraperez au moment de l'exécution - finalement, mais c'est très mauvais. Si vous configurez un tas de pointeurs de fonction et que vous associez le temps de compilation à la taille de la table, vous faites mieux, mais c'est un autre niveau plus profond que l'instruction switch. Et tout cela sort par la fenêtre dès que vous avez une deuxième place dans le code qui doit vérifier l'état.

Il est simplement beaucoup plus facile, une fois que vous avez configuré votre classe d'interface, de laisser le compilateur gérer en interne tout le code indésirable des états d'activation. Vous ajoutez la classe ne vous inquiétez pas pour tout autre code tant que vous suivez l'interface.