czwartek, 14 marca 2013

callbacks - composition vs.inheritance

The problem - how to implement base mechanism that contains callback.
For example - MessageHandler class - allows user to start action (via callback) when appropriate message arrives.

class MessageHandler
{
...
public:
   virtual void doMessageHande(const Message &m) = 0;
   MessageHandler(MessageHandlerRegistry &reg)
   {
      reg.registry(*this); // now registry can call doMessageHandle() when message arrives
   }
...
};



Solution 1 - base class (inheritance)

Problem - for example when class Auses MessageHandler. Class A is also base class for class B that should also use message handler (for its own purpose). In such situation callback implemented as virtual function will improperly end in class B also for message handler from class A.

class A : private MessageHandler
{
...
   void doMessageHandle(const Message &m); // won't get called for B objects
   A(MessageHandlerRegistry &reg)
   : MessageHandler(reg) // registry A object as message handler
...
};
class B : public A, private MessageHandler
{
...
   void doMessageHandle(const Message &m);
   B(MessageHandlerRegistry &reg)
   : A(reg),
     MessageHandler(reg) // registry B object as message handler
...
};


If somewhere in program we have MessageHandler pointer or reference that points to B object (e.g. message registry), doMessageHandle from B will be invoked. This is normal behavior of virtual functions. Note that here we do not want to have one base class object therefore virtual inheritance is not proper approach. What we need is to allow B object to handle messages in both MessageHandlers (from A and B classes).


Solution 2 - composition - additional class

To break virtual function chain have to break inheritance and use additional class to contain message handler.
Even in solution 1 we had private inheritance it suggest that this is not the inheritance (class A is not a MessageHandler). Now we know that we have to go further.
To break inheritance chain we can define additional classes as inheritance endpoints. Here we have embedded class AMsgHandler that encapsulates callback.

class A
{
...

   void doMessageHandle(const Message &m);
   class AMsgHander : private MessageHandler
   {
      A &_a;
      void doMessageHandle(const Message &m)
      {
         _a.doMessageHandle(m);
      }
   public:
      AMsgHander(MessageHandlerRegistry &reg, A &a)
      : MessageHandler(reg),
        _a(a)
      {}

   };
   AMsgHandler msg_handler;
public:

   A(MessageHandlerRegistry &reg) : msg_handler(reg, *this)
...
};


This solution will work but here we have lot of additional code what make the code not very convenient to extend.


Solution 3 - composition - generalized callback - delegate

The only element that has to be excluded is callback. Therefore it is useful to have some common solution for all callback-related problems. The answer is delegate - the world of member function pointers and not so commonly known '.*' and '->*' operators and template programming.
The point is to have one line solution that makes code clear and easy to extend and maintain.
Delegates are deeply explored topic in C++ (see references). Problem with general callback is that they are general, what in such strict language as C++ is difficult to achieve.
If we use some of existing delegate implementations (e.g. BOOST) we can simplify solution.
Below we have solution that do not change MessageHandlerRegistry - there is still MessageHandler class that acts as interface between registry and end-user class. If we change registry class then we can get rid of MessageHandler class and use directly callback in registry (and also get rid of "handler" member from A class).


class MessageHandler
{
...
public:

   typedef function1<void, const Message&> MessageCallback;
private:
   MessageCallback _callback;
public:
   void doMessageHande(const Message &m)
   {
      _callback(m);
   }
   void MessageHandler(MessageHandlerRegistry &reg, const MessageCallback &c)
   : _callback(c)
   {
      reg.registry(*this);
   }
...
};


class A
{

private:
   MessageHandler handler;
   void doMessageHandle(const Message &m);
...
public:

   A(MessageHandlerRegistry &reg)
   : handler(reg, std::bind1st(std::mem_fun(&A::
doMessageHandle), this))
...
};





Of course presented problem is simplified. In real world it can be obscured. For example the MessageHandler class can be used by TimeoutHandler class and also by some user's DoItRight class. DoItRight class is also using TimeoutHandler. In the end DoItRight object gets MessageHandler's callbacks from TimeoutHandler part and also from its own MessageHandler part. In case of inheritance we end up in problem presented in solution 1.


References

http://www.boost.org/doc/html/function.html
http://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible
http://www.codeproject.com/Articles/11015/The-Impossibly-Fast-C-Delegates

Brak komentarzy:

Prześlij komentarz