Find a command handler from a command type in modern C++

Viewed 215

I have a bunch of structs (commands) with a different names and data. And I have a bunch of command handlers. Client code sends a struct to the intermediator, and the intermediator should find a concrete command handler based on a command (struct) type. How can I do this without using a switch/if statements in modern C++?

#include <iostream>

struct Command {};

struct CommandA : Command
{
    int a = 32;
};

struct CommandB : Command
{
    double b = 56.43;
};

class ACommandHandler
{

public:
    void Process(CommandA command) { std::cout << command.a; }
};

class BCommandHandler
{

public:
    void Process(CommandB command) { std::cout << command.b; }
};

class Intermediator
{
public:
    void Delegate(Command command)
    {
    //How can I choose a concrete command handler here?
    }

private:
    ACommandHandler handlerA;
    BCommandHandler handlerB;
};

int main()
{
    Intermediator intermediator;
    CommandA a;
    intermediator.Delegate(a);
    return 0;
}
2 Answers

Hm. In my understanding your design or your approach to solve the problem is maybe wrong. Maybe, we are talking about a XY-Problem here.

It would be really interesting to understand what you really want to achieve.

Then, my next observation: I think you do not yet fully understand the meaning of polymorphism or how to use it correctly.

In your example code, there is nothing with polymorphism at all. There are no virtual functions and such.

And your CommandHandler are not derived from a common base class. At the first glance, it looks like there is a relation between the CommandHandler and the Command. But there is not. There is just an equal naming. For example BCommandHandler could also have a process function that works on a Type CommandA. This is somehow a strange concept. There should be a relation between the Handler and the Command.

Best would be to have the process function as part of the command class. That would be a more meaningful design and would eliminate nearly all problems. But as said: I do not know the reason, why you have chosen such a design.

Next, even if we would have a class hierarchy for the CommandHandler and try to invoke a virtual function through the base class pointer of a CommandHandler, we would still have, as parameter of the function, a base class pointer to the Command.

And this would require a so called double dispatch functionality. So, polymorphism on the called object and the given parameter. Unfortunately, the C++ language does not support double dispatch mechanisms. You can emulate it by employing the Visitor pattern from the GOF. But this pattern is notoriously difficult to understand and implement and creates problems with circular references. One very good solution can be found in ”Modern C++ Design: Generic Programming and Design Patterns Applied” by Andrei Alexandrescu. There you can read about the Acyclic Visitor Pattern. A very good explanation.

Another solution would use templates for static/compile time polymorphism. But you said that you have many commands. And I fear you would get some substantial code bloating.

But since you have some fixed relation between a handler and a command you could maybe solve your problem using a std::map and some RTTI mechanisms. And with dynamic_cast

Not nice and probably expensive, but could be used in the below way:

#include <iostream>
#include <map>
#include <string>
#include <utility>

struct Command { virtual void someDummyVirtualFunctionDoingNothing() {} };

struct CommandA : public Command { int i{ 42 }; };
struct CommandB : public Command { double d{ 43.2 }; };
struct CommandC : public Command { unsigned u = 44u; float f{ 45.6 }; };

struct CommandHandler {
    virtual void process(Command*) = 0;   // pure virtual function. Make Base class abstract
};
struct CommandHandlerA : public CommandHandler {
    void process(Command* com) {
        if (CommandA* ca = dynamic_cast<CommandA*>(com))
            std::cout << "Command Handler A:  " << ca->i << '\n';
    }
};
struct CommandHandlerB : public CommandHandler {
    void process(Command* com) {
        if (CommandB* cb = dynamic_cast<CommandB*>(com))
            std::cout << "Command Handler B:  " << cb->d << '\n';
    }
};
struct CommandHandlerC : public CommandHandler {
    void process(Command* com) {
        if (CommandC* cc = dynamic_cast<CommandC*>(com))
            std::cout << "Command Handler C:  " << cc->u << '\t' << cc->f << '\n';
    }
};
class Intermediator
{
public:
    void delegate(Command* command)
    {
        const std::string key{ typeid(*command).name() };
        if (dispatcher.find(key) != dispatcher.end()) {
            dispatcher[key]->process(command);
        }
    }
    // Commands are just dummies and used to get the type
    void addDelegate(CommandHandler* handler, Command* command) {
        const std::string key{ typeid(*command).name() };
        dispatcher[key] = handler;
    }
protected:
    std::map<std::string, CommandHandler*> dispatcher{};
};

int main() {
    // Define instances of command classes 
    Command* commandA = new CommandA();  // Dummy. Only needed for type deduction
    Command* commandB = new CommandB();  // Dummy. Only needed for type deduction
    Command* commandC = new CommandC();  // Dummy. Only needed for type deduction
    Command* commandAA = new CommandA();
    Command* commandBB = new CommandB();
    Command* commandCC = new CommandC();

    // Define Instances of command handler
    CommandHandler* commandHandlerA = new CommandHandlerA();
    CommandHandler* commandHandlerB = new CommandHandlerB();
    CommandHandler* commandHandlerC = new CommandHandlerC();

    // Add delegate function for types.
    // All pointers here are pointer to the base classes.
    // Here the commands are just dummies and only used to get the type.
    Intermediator im{ };
    im.addDelegate(commandHandlerA, commandA);
    im.addDelegate(commandHandlerB, commandB);
    im.addDelegate(commandHandlerC, commandC);

    // Call delegate. Please note. The commands are pointers to the abstract base Class 
    im.delegate(commandAA);
    im.delegate(commandBB);
    im.delegate(commandCC);

    return 0;
}

Please note: Raw pointer for owned memory should be replace by std::unique_ptr or other smart pointers.

And, as said above, I still think that this is the wrong design.

Maybe an Abstract Factory could help you.

#include <iostream>
#include <type_traits>

struct CommandA
{
    auto Do() const { return m_data; }
private:
    int m_data = 42;
};

struct CommandB
{
    auto Do() const { return m_data; }
private:
    float m_data = 3.1415f;
};

struct CommandC
{
    auto DoC() const
    {
        std::cout << "C" << std::endl;
    }
};

class Intermediator
{
public:
    template<typename command_t>
    auto Delegate(const command_t& command)
    {
            return command.Do();
    }

    // this will be the most specialized handler for CommandC
    auto Delegate(const CommandC& command)
    {
        return command.DoC();
    }
};

int main()
{
    CommandA a;
    CommandB b;
    CommandC c;

    Intermediator intermediator;
    
    auto value = intermediator.Delegate(a);
    std::cout << value << std::endl;

    auto pi = intermediator.Delegate(b);
    std::cout << pi << std::endl;

    intermediator.Delegate(c);

    return 0;
}
Related