2

I'm writing a tool which enables a user to interact with a bit of hardware by changing settings and then streaming information.

To do this I have a couple of threads running: EquipmentInterface and DataProcessor which are connected by a Queue.

The EquipmentInterface thread has methods to alter settings on the equipment (Rotate and Refocus for example) and the resulting information (CurrentAngle and CurrentFocalDistance) is added to the Queue. Once the settings are correct there are methods to StartStreaming and StopStreaming and once streaming starts, data from the equipment is packetised and added onto the queue.

All of the information placed on the queue derives from a single BaseMessage class which includes an indication of the message type. I then have derived message types for angles, focal distances, beginning and ending streaming and of course, the data itself.

The DataProcessor listens to the other end of the Queue and depending on the current angle / focal distance, processes the subsequent data.

Now, the thing is, I have a function in the data processor which uses a switch statement to type-check the messages coming in. Those messages are then down-casted to the appropriate type and passed to an appropriate handler. In reality, there's more than just a DataProcessor listening to a single queue, but in fact multiple listeners on multiple queues (some store to disk, some display information on a gui). Every time I add some information I have to create a new BaseMessage derived class, add a new type to that base class and then update the switch statements in each of the consumers to cope with the new message.

Something about this architecture feels wrong to me and I've been reading a lot about down-casting recently. From what I've seen, the general consensus seems to be that what I'm doing is a bad code smell. I've seen a suggestion which use Boost, but they don't look any cleaner than the switch statement to me (maybe I'm missing something?).

So my question is: Should I be trying to avoid the switch-statement / downcasting solution and if so, how?

My implementation is in C++/CLI so either .net or C++ solutions are what I'm after.

Edit - Based on the comments from iammilind and stfaanv, is this the sort of thing you're suggesting:

class QueuedItem
{
public:
    QueuedItem() { }
    virtual ~QueuedItem() { }

};

class Angle : public QueuedItem
{
public:
    Angle() {}
    virtual ~Angle() { }
};

class FocalLength : public QueuedItem
{
public:
    FocalLength() {}
    virtual ~FocalLength() { }
private:

};


class EquipmentHandler
{
protected:
    virtual void ProcessAngle(Angle* angle) {}; 
    virtual void ProcessFocalLength(FocalLength* focalLength) {};   

public:
    void ProcessMessages(QueuedItem* item)
    {
        Angle* pAngle = dynamic_cast<Angle*>(item);
        if( pAngle != NULL )
        {
            ProcessAngle(pAngle);
        }
        FocalLength* pFocalLength = dynamic_cast<FocalLength*>(item);
        if( pFocalLength != NULL )
        {
            ProcessFocalLength(pFocalLength);
        }

    }
};

class MyDataProcessor : public EquipmentHandler
{
protected:
    virtual void ProcessAngle(Angle* angle) override { printf("Processing Angle"); }
    virtual void ProcessFocalLength(FocalLength* focalLength) override { printf("Processing FocalLength"); };   
};


int _tmain(int argc, _TCHAR* argv[])
{

    // Equipment interface thread...
    FocalLength* f = new FocalLength();
    QueuedItem* item = f; // This gets stuck onto the queue

    // ...DataProcessor thread (after dequeuing)
    QueuedItem* dequeuedItem = item;

    // Example of a DataProcessor implementation.
    // In reality, this would 
    MyDataProcessor dataProc;
    dataProc.ProcessMessages(dequeuedItem);

    return 0;
}

...and can it be simplified? The ProcessMessages feels a bit clunky but that's the only way I could see to do it without a switch statement and some sort of enumerated message type identifier in the base class.

Community
  • 1
  • 1
Jon Cage
  • 36,366
  • 38
  • 137
  • 215
  • 1
    It seems like You are in desperate need of *Dynamic Polymorphism and Dynamic dispatch*. – Alok Save Apr 11 '12 at 07:12
  • Why don't you declare a `virtual` function in a base class and implement in all its children. – iammilind Apr 11 '12 at 07:13
  • @iammilind: That would certainly save re-implementing the switch statement multiple times but doesn't get rid of it completely. Is that the most efficient way to determine which handler to call? – Jon Cage Apr 11 '12 at 07:24
  • @Als: Can you elaborate? – Jon Cage Apr 11 '12 at 07:32
  • What @iammilind, said & yes that is the way to do it. – Alok Save Apr 11 '12 at 07:32
  • @Als: I just found something called the visitor pattern. Is this what you're talking about: http://stackoverflow.com/questions/3254788/how-visitor-pattern-avoid-downcasting ...or is that over-complicating things? – Jon Cage Apr 11 '12 at 07:37
  • @JonCage: it does get rid of the switch and downcasting completely, all that the queue-reader has to do is call the virtual function. So the only thing to do is define a class per message with the function that accesses the destination object. With std::bind and std::function, you can even avoid making a class per message, because the data of the message class are mostly function parameters anyway. – stefaanv Apr 11 '12 at 07:43
  • @JonCage: visitor is over complicating things because you just need to dispatch. With visitors, you introduce dependencies and must overload the visit function for each message. – stefaanv Apr 11 '12 at 07:46
  • You don't need to overload the visit function in every visitor if you use a base class for your visitor which implements all visiting methods as noops. See my answer. – fjardon Apr 11 '12 at 09:25
  • @stefaanv: I've added an example of what I think you're talking about. The only way I can see you can get rid of the switch statement is by multiple attempts to identify the handler to call is with repeated dynamic cast attempts. I've not benchmarked this, but it feels like it would be slow given a high message throughput? – Jon Cage Apr 11 '12 at 21:19
  • I added a sample code to show how to implement the visitor. Please remark that with this scheme when you create a new equipment handler inheriting from `EquipmentVisitor` you only need to implement the method handling the message you're interested in. The other messages will pass through the `EquipmentVisitor` base class virtual methods wich are no ops. – fjardon Apr 12 '12 at 08:00

3 Answers3

2

You could try a visitor design pattern: http://en.wikipedia.org/wiki/Visitor_pattern

Each DataProcessor would inherit from a BaseVisitor class, which defines virtual method for handling each type of Message. Basically these methods are just noop.

When you define a new message type you add a new virtual method with a noop implementation for this message type in the BaseVisitor. Then if a child DataProcessor class wants to process this message type you override the virtual method in this DataProcessor only. All other DataProcessorremain untouched.

    #include <iostream>


    class FocalLength;
    class Angle;
    class EquipmentVisitor;

    class QueuedItem
    {
    public:
            QueuedItem() { }
            virtual ~QueuedItem() { }

            virtual void AcceptVisitor(EquipmentVisitor& visitor) = 0;
    };

    class EquipmentVisitor
    {
    public:
            virtual ~EquipmentVisitor() {}

            virtual void Visit(FocalLength& item) {}
            virtual void Visit(Angle& item)       {}

            void ProcessMessages(QueuedItem* item)
            {
                    item->AcceptVisitor(*this);
            }
    };

    class Angle : public QueuedItem
    {
    public:
            Angle() {}
            virtual ~Angle() { }

            void AcceptVisitor(EquipmentVisitor& visitor) { visitor.Visit(*this); }
    };

    class FocalLength : public QueuedItem
    {
    public:
            FocalLength() {}
            virtual ~FocalLength() { }

            void AcceptVisitor(EquipmentVisitor& visitor) { visitor.Visit(*this); }
    private:

    };

    class MyDataProcessor : public EquipmentVisitor
    {
    public:
            virtual ~MyDataProcessor() {}

            void Visit(Angle& angle)             { std::cout << "Processing Angle" << std::endl; }
            void Visit(FocalLength& focalLength) { std::cout << "Processing FocalLength" << std::endl; }
    };


    int main(int argc, char const* argv[])
    {
            // Equipment interface thread...
            FocalLength* f    = new FocalLength();
            QueuedItem*  item = f; // This gets stuck onto the queue

            // ...DataProcessor thread (after dequeuing)
            QueuedItem* dequeuedItem = item;

            // Example of a DataProcessor implementation.
            // In reality, this would
            MyDataProcessor dataProc;
            dataProc.ProcessMessages(dequeuedItem);

            return 0;
    }
fjardon
  • 7,921
  • 22
  • 31
0

You could do any of the following:

Delegate the handling code (as in each case in your switch statement) to Handler objects -- either a hierarchy of HandlerBase objects, or completely unrelated types.

You then have your messages keep a reference to the Handler object (if it's a hierarchy, you can do it at BaseMessage level, if unrelated objects, then as part of the individual specialised message types), to which you can then pass them when you're handling them, through a BaseMessage::Handle() method. Edit: This method is NOT virtual.

Of course, if you go down the path of HandlerBase hierarchy, you'll still need to static_cast the messages back to whatever type they are, but that should be fine: they should only be created with their own Handler (that's supposed to know their types) anyway.

Example:

// BaseMessage.hpp
#include <iostream>

class BaseMessage
{
public:
  BaseMessage(HandlerBase* pHandler);
  : m_pHandler(pHandler)
  {}

  virtual ~BaseMessage()
  {}

  void  SetHandler(HandlerBase* pHandler)
  {
    m_pHandler = pHandler;
  }

  void  Handle()
  {
    assert(m_pHandler != 0);
    m_pHandler->Handle(this);
  }

protected:
  HandlerBase*  m_pHandler; // does not own it - can be shared between messages
};

// HandlerBase.hpp
class HandlerBase
{
public:
  HandlerBase()
  {}

  virtual ~HandlerBase()
  {}

  virtual void Handler(BaseMessage* pMessage) =0;
}

// message and handler implementations
class AMessage: public BaseMessage
{
public:
  AMessage(BaseHandler* pHandler)
    : BaseMessage(pHandler)
  {}

  ~AMessage() {}

  void  DoSomeAness()
  {
    std::cout << "Being an A..." << std::endl;
  }
};

class AHandler
{
public:
  AHandler()
  {}

  virtual ~AHandler()
  {}

  virtual void Handle(BaseMessage* pMessage)
  {
    AMessage *pMsgA(static_cast<AMessage*>(pMessage));
    pMsgA->DoSomeAness();
  }
};

class BMessage: public BaseMessage
{
public:
  BMessage(BaseHandler* pHandler)
    : BaseMessage(pHandler)
  {}

  ~BMessage() {}

  void  DoSomeBness()
  {
    std::cout << "Being a B..." << std::endl;
  }
};

class BHandler
{
public:
  BHandler()
  {}

  virtual ~BHandler()
  {}

  virtual void Handle(BaseMessage* pMessage)
  {
    BMessage *pMsgB(static_cast<BMessage*>(pMessage));
    pMsgB->DoSomeBness();
  }
};


// the thread
static std::list<BaseMessage*> msgQueue;

int HandlerThread(void *pData)
{
  while(true)   // find some more sophisticated way to break
  {
    while(!msgQueue.empty())
    {
      msgQueue.front()->Handle();
      msgQueue.pop_front();
    }
    // delay and stuff
  }
  return 0;
}

int main(int argc, char** argv)
{
  start_thread(HandlerThread, 0); // your favorite API here

  AHandler  aHandler;
  BHandler  bHandler;

  msqQueue.push_back(new AMessage(&aHandler));
  msqQueue.push_back(new BMessage(&bHandler));
  msqQueue.push_back(new BMessage(&bHandler));
  msqQueue.push_back(new AMessage(&aHandler));
  msqQueue.push_back(new AMessage(&aHandler));
  msqQueue.push_back(new BMessage(&bHandler));
  msqQueue.push_back(new AMessage(&aHandler));
  msqQueue.push_back(new BMessage(&bHandler));

  return 0;
}

Edit: yes, in essence, this is the visitor pattern.

zyndor
  • 1,418
  • 3
  • 20
  • 36
  • Does that mean that each object needs to know (when it's created) what's going to handle it? Could you provide a concrete example? – Jon Cage Apr 11 '12 at 07:28
  • added some example code. the objects don't absolutely have to know about their handlers, but it will be a pain to SetHandler()s after they're created and added to the queue, mostly because of how queues work and because they just appear to be BaseMessage*s. You can add some other facility that knows about your message types, mapped to whatever information, and can set their handlers respectively. – zyndor Apr 11 '12 at 09:21
  • @iCE-9: your solution seems to be halfway between message dispatching and double dispatch, both of which are more elegant and avoid the casting. If your messages are specific to a handler, just use the specific handler in the message and do the right call to the handler via a virtual handle() function. If the message is potentially for any handler, but with different result, do double dispatch by a virtual handle() (or accept) function in the message and an overloaded (per message) act() (or visit) function in the handler called by the msg::handle() function. – stefaanv Apr 11 '12 at 09:26
  • @stefaanv: it being a replacement of a switch statement is anything but dynamic: one message ('case:') can be handled by exactly one handler (the body of the 'case:') - but multiple cases / message types can be routed to the same handler. The handler does not have to call member functions of the messages, it just goes to express that the stuff that's happening is specific to the type of the message. – zyndor Apr 11 '12 at 09:42
  • @iCE-9: I'm sorry, but I don't understand your last comment. However, as I see it, the OP can decide to use 1. direct dispatching (n msgs per handler), 2. dispatching per handler with casting (n msgs to m handlers), 3. double dispatch (n msgs to m handlers, see fjardon's answer). – stefaanv Apr 11 '12 at 10:03
  • 1
    @stefaanv: sorry, didn't have a chance to reply properly. I meant that the code that Jon is trying to replace isn't 'dynamic', and the mapping between message types and handlers is many-to-one, which is why I didn't worry about Double/Dynamic dispatch. Btw, what do you mean by "If your messages are specific to a handler, just use the specific handler in the message and do the right call"? (A/BMessage's derivatives might also use their respective handlers, this is what I meant by the last sentence.) – zyndor Apr 11 '12 at 18:18
  • Maybe I didn't make the problem clear, but it's many to many as I stated in the question `"In reality, there's more than just a DataProcessor listening to a single queue, but in fact multiple listeners on multiple queues (some store to disk, some display information on a gui)."`.. ...so there's no way to pass in the handler in advance. Having to pass in handlers from the other end of the queue seems like a messy way to do things still... – Jon Cage Apr 11 '12 at 21:03
  • see my answer for dispatching handler specific messages without casting or switch case. Passing handler in advance is not that messy. – stefaanv Apr 12 '12 at 07:10
0

Simplest message handling according to me for sending 4 messages to 2 handlers:

#include <iostream>
#include <queue>
#include <memory>

class HandlerA
{
public:
  void doA1() { std::cout << "A1\n"; }
  void doA2(const std::string& s) { std::cout << "A2: " << s << "\n"; }
};

class HandlerB
{
public:
  void doB1() { std::cout << "B1\n"; }
  void doB2(const std::string& s) { std::cout << "B2: " << s << "\n"; }
};


class BaseMsg
{
public:
  virtual ~BaseMsg() {}
  void send();
  virtual void handle() { execute(); }
  virtual void execute() = 0;
};

typedef std::shared_ptr<BaseMsg> Msg;
class Medium
{
  std::queue<Msg> queue;
public:
  void send(Msg msg) { queue.push(msg); }
  void process()
  {
    while (! queue.empty())
    {
      std::cout << "Processing\n";
      queue.front()->handle();
      queue.pop();
    }
  }
};

class BaseMsgHndlrA : public BaseMsg
{
protected:
  HandlerA& ha;
public:
  BaseMsgHndlrA(HandlerA& ha_) : ha(ha_) { }
};

class BaseMsgHndlrB : public BaseMsg
{
protected:
  HandlerB& hb;
public:
  BaseMsgHndlrB(HandlerB& hb_) : hb(hb_) { }
};

class MsgA1 : public BaseMsgHndlrA
{
public:
  MsgA1(HandlerA& ha_) : BaseMsgHndlrA(ha_) { }
  virtual void execute() { ha.doA1(); } 
};

class MsgA2 : public BaseMsgHndlrA
{
public:
  MsgA2(HandlerA& ha_) : BaseMsgHndlrA(ha_) { }
  virtual void execute() { ha.doA2("Msg A2"); } 
};

class MsgB1 : public BaseMsgHndlrB
{
public:
  MsgB1(HandlerB& hb_) : BaseMsgHndlrB(hb_) { }
  virtual void execute() { hb.doB1(); } 
};

class MsgB2 : public BaseMsgHndlrB
{
  std::string s;
public:
  MsgB2(HandlerB& hb_, const std::string s_) : BaseMsgHndlrB(hb_), s(s_) { }
  virtual void execute() { hb.doB2(s); } 
};

int main()
{
  Medium medium;
  HandlerA handlerA;
  HandlerB handlerB;

  medium.send(Msg(new MsgA1(handlerA)));
  medium.send(Msg(new MsgA2(handlerA)));
  medium.send(Msg(new MsgB1(handlerB)));
  medium.send(Msg(new MsgB2(handlerB, "From main")));

  medium.process();
}

This only uses virtual functions to dispatch to the right handler with some parameters.
The handle() function is not strictly needed, but helpful when defining hierarchy of messages.
A generic message could hold an std::function that can be filled in with bind, so actual functions with parameters can be sent instead of making a message class per queued action.
To hide the actual sending, the handlers can do the sending themselves, so they can be accessed immediately from the sending thread.

If however several messages need to be sent to more handlers, double dispatch (visitor) can be used.

stefaanv
  • 14,072
  • 2
  • 31
  • 53
  • As I said in the question, the full application involves multiple messages (all derived from a single common base) being sent to a variety of handlers. – Jon Cage Apr 12 '12 at 15:38
  • And this answers your question (I showed two handlers accessed by the messages). However, as I already commented, you have the choice between this single dispatch solution and fjardon's double dipatch solution and even a casting solution, so it's your call and apparently double dispatch it is. – stefaanv Apr 13 '12 at 06:35