2

I have a library, which sends and receives a set of binary messages and parses them.

So far I’ve used inheritance for my design, i.e.

class BaseMsg 
{
    // init msg from rx'd binary stream
    virtual bool fromBinary(std::vector<char> b) = 0;
    // write msg content to binary stream to tx
    virtual std::vector<char> toBinary() const = 0;

   MsgType type;
};

class MsgA : public BaseMsg 
{
    MsgA()
    {
        type = MsgType::A;
    }

    bool fromBinary(std::vector<char> b)
    {
        // parse binary stream here and init members
    }

    std::vector<char> toBinary() const
    {
        // encode members to binary stream here
    }
private:
    std::string freetext;
    // more members
};

class MsgB : public BaseMsg
{
    MsgB()
    {
        type = MsgType::B;
    }

    bool fromBinary(std::vector<char> b)
    {
        // parse binary stream here and init members
    }

    std::vector<char> toBinary() const
    {
        // encode members to binary stream here
    }
private:
    double latitude;
    double longitude;
    // more members
};

In addition I have a messaging class Messenger, which has methods to send and receive, i.e.

bool sendMessage(std::unique_ptr<BaseMsg> msg)
{
    std::vector<char> bin = msg->toBinary();
    // send bin
}

std::unique_ptr<BaseMsg> receive(std::vector<char> receivedBinaryStream)
{
    if (/*check in binary stream, if msg is MsgA*/)
    {
        auto msg = std::make_unique<MsgA>();
       msg->fromBinary(receivedBinaryStream);
    return std::move(msg);
    }
if (/*check in binary stream, if msg is MsgB*/)
    {
        auto msg = std::make_unique<MsgB>();
       msg->fromBinary(receivedBinaryStream);
    return std::move(msg);
    }
}

This works quite well, but when using the library I end up casting a lot from the base class to a specific sub class. This is especially after reception of a message and using the message in my application.

// 
auto msg = messenger.receive(binStream);

if (msg->type == MsgType::A)
{
    // cast to MsgA and continue processing
}
else if (msg->type == MsgType::B)
{
    // cast to MsgB and continue processing
}

So the question is, if there's an alternative architecture/design, which would avoid the casts in the last code sample.

The question is not for details of the implementation, but if there's a better design than currently using inheritance.

edit:

Messages have different parameters, e.g. MsgA provides free text, MsgB provides location info of an object, etc.

Therefore when a message is consumed by the application, it's casted to its sub class to obtain the type specific parameters.

  • 1
    I think you'd want to share more with us. The main thing I'd be interested in is where you discover that the message is type A, so instantiate a MsgA, but presumably soon thereafter, treat it as BaseMgs, loosing the concrete type. – Erik Eidt Nov 9 '18 at 20:08
  • 1
    There's not enough information in your question to make it reasonably answerable here. At a minimum, we would need to see actual class names and descriptions of their functionality, not foobar names, and perhaps a bit more code. – Robert Harvey Nov 9 '18 at 20:10
  • 2
    Also, doesn't C++ support runtime polymorphism? You shouldn't need to cast much. If you are, it means that your subclasses aren't overriding your virtual methods in BaseMsg properly. – Robert Harvey Nov 9 '18 at 20:12
  • 1
    Honestly I don't get your hostility. If the question is unclear, please explain what should be clarified. If you check the edit history I made multiple edits to improve the question, if that's still not sufficient I'm more than happy to add more clarification. – Simon Nov 9 '18 at 21:43
  • 1
    Sorry for not making the question clear enough yet, I'm trying to make it more clear. Question was not aimed at the details of the implementation, but rather asking if there's maybe a design which could be better. The provided code is a reduction from a more complex real-world code with maybe too much reduction at the moment. – Simon Nov 9 '18 at 21:59
2

First, separate messages from their handlers.

Instead of having MessageTypeA handle its own parsing to and from binary; introduce an intermediary MessageHandlerTypeA which does this. This way you can reduce the messages themselves to Plain Ol' Data types or - at the very least - simple data containers, and you can keep the actual handlers instanced throughout instead of creating them new each time.

Then in your message receiving code you can register for each message type which MessageHandler to pass the binary data to. This is extensible from outside the library since you can then expose the registration function to allow new messages to be arbitrarily interpreted.

Second, use callbacks to do further message handling.

Instead of the receive and then process approach you are using, have the MessageHandler decode the message into the appropriate message type and then call any code that needs to know about that particular message. Your calling code, then, rather than interpreting the message simply registers a callback with the handler and then receives a callback with the interpreted message whenever that message is received.

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.