0

I know this question has to have been answered in countless ways before, but I would appreciate some help pointing me in the correct direction.

I am developing an embedded system that receives "commands" over a communication line. Obviously, in the OOP world, a specific type of "command" could be represented as a specific type of class which inherits from some base class. For example:

class Command;
class LEDControlCommand : public Command;
class EchoCommand : public Command;

The commands come into the communication line as a serialized string of bytes and thus must be deserialized and "handled" by the receiver depending on their type. Serialization is simple enough to solve in an OOP way by, for example, introducing a virtual serialize() method in the Command class that the specific types of commands override.

class Command {
public:
    virtual byte_array serialize() = 0;
};

Assuming byte_array is some type that can hold/represent an arbitrary array of bytes.

Deserialization presents more of a problem.

It seems to me that this calls for some sort of factory which takes an array of bytes as an input and returns a newly-created instance of Command. internally, the factory could either use a big switch statement (which I know is a code smell) or, better yet, a hash map and the "prototype" pattern.

class CommandFactory {
public:
    std::unique_ptr<Command> create(const byte_array& bytes);
};

So here's the problem, I know have a specific instance of a Command hidden as a pointer to a generic Command. From what I can tell, in the ideal OOP world, you would design these classes in such a way that their individual/soecific behavior is encapsulated within the class itself. For example:

class Interface {
public:
    virtual const char* name() const = 0;
};

class Dog : public Interface {
public:
    const char* name() const override { return "Dog"; }
};

But I don't see how this works in the case of a command which needs to be "routed" to some other piece of code to be handled in a very specific way. For example, if an LEDControlCommand instance is parsed by the factory, that object needs to be send to the object in charge of controlling the LEDs. In addition, this class needs to be aware that it is specifically receiving an LEDControlCommand class and not just some generic Command class.

The naïve approach would be to have some virtual type() function which returns the type of the object and allows me to downcast (again probably using some if/switch statement which is bad).

So how do you solve a problem like this in a manner which is "OOP-friendly" and simple to maintain/extend? If you could point me towards some existing articles that would be very helpful as well.

3
  • 2
    "the factory could either use a big switch statement (which I know is a code smell) [...]" It is not a code smell to use a switch case in a factory. The smell comes from piloting behaviour using a switch case (e.g. on the type of the object, or on an enum). Sep 22 at 14:30
  • @VincentSavard Okay, that's good to know. So, in my case, as I correctly assumed, it would be a code smell to use a switch statement to perform different behavior depending on the type of object that was produced by the factory. Sep 22 at 14:33
  • 1
    If you are free to choose your own wire/serialisation format, then this sounds like a good use-case for Google Protocol Buffers (protobufs). Sep 22 at 17:25
3

You're asking two questions that are only tangentially related (I mean, they're closely related in your problem domain, but their solutions are independent).

  1. Deserialize a concrete Command subclass
  2. Send commands to the components they're supposed to affect

Decouple these in your mind. And your code, obviously, but fix the understanding and the design will follow.

I'm not going to address the deserialization part, because it seems like you have something that will work.

Now, for routing, it depends mostly on some non-functional requirements. Options are:

  1. The simplest thing you can possibly do: have every component that can be controlled by any incoming Command subscribe as an Observer. Pass every Command to every Observer, and let them decide (say, via double-dispatch like the Visitor) what to do about it.

    This is potentially expensive if the hit rate is too low (ie, lots of Observer callbacks happen that don't accomplish anything).

    eg. subscribeToAllCommands(handler)

  2. If you have few distinct Command types, just add a dedicated Observer subscription for each.

    eg. subscribeToLEDCommands(led_handler)

  3. If you have many Command types, but they're grouped into a few overlapping categories, you can use something like a bitmask to quickly decide which Observers should handle which Commands (compare something like select or poll)

    eg. subscribeToCommands(CMD_LED|CMD_ECHO, custom_led_echo_handler)

1

I do have a (possibly crazy) idea that may be a solution. It is based upon the Abstract Factory design pattern. Assuming commands are differentiated by a single byte "id" field, I can introduce an intermediate command class called AnyCommand as such:

class Command {
public:
   virtual uint8_t id() const = 0;
};

class AnyCommand : public Command {
public:
    uint8_t id() const override { return _id; }

private:
    uint8_t _id;
    byte_array _data;
};

Now, for my first deserialization step, I just need to deserialize a byte_array into a AnyCommand object.

Next my "factory" could keep a map of ids to abstract factories that "handle" AnyCommand objects with these ids. For example:

class AbstractCommandFactory {
public:
    virtual void handle(const AnyCommand& c) = 0;
};

class CommandFactory {
public:
    AnyCommand deserialize(const byte_array& bytes);
    void handle(const AnyCommand& c);

private:
    std::map<uint8_t, AbstractCommandFactory*> _handlers;
};

class LEDControlCommandHandler {
public:
    virtual void handle(const LEDControlCommand& c) = 0;
};

class LEDControlCommandFactory : public AbstractCommandFactory {
public:
    void handle(const AnyCommand& c) override;

private:
    std::list<LEDControlCommandHandler*> _handlers;
};

Where the handle method in LEDControlCommandFactory will both a) attempt to further deserialize AnyCommand into an LEDControlCommand and b) send this command to the appropriate all of the registered LEDControlCommandHandler objects.

Any thoughts on this solution?

1
  • This is just a regular abstract factory and the AnyCommand intermediate step isn't adding anything. You have a byte buffer to start with, just choose your factory based on the type id byte at the beginning and get a Command abstract base-class pointer as usual.
    – Useless
    Sep 23 at 10:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.