July 28, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Event Dispatching: One Size Doesn't Fit All

  • November 23, 2005
  • By Radu Braniste
  • Send Email »
  • More Articles »

There are two dispatching chains of types W and WW. Registration at run-time (equivalent to storing both in the same container) is impossible because containers should be homogeneous.

But there is hope—the same idiom used in static interfaces [6] can be used to mask a type that can be recovered at a later time. This piece of magic is implemented in RelayInterface that conceals W and WW. It is now possible to use the following construct:

std::vector<RelayInterface<E> >

allowing containers discriminated by event types only. Once the chains are registered, a single "dispatch(evt);" call takes care of dispatching the event through all the registered chains. Because the process never tries to hide the true type of the event, it is fully type safe; the compiler rejects any attempt to use an unhandled event. The obvious downside is the need to register every chain against every event and this can be sometimes a very laborious task.

A third attempt simplifies the previous model even further [7]:

template <class D>
struct Relay
{
   template<class E>
   static void relay(const E& e)
   {
      D::relay(e);
   }
};

template <>
struct Relay <void>
{
   static void relay()
   { }
   template <class E>
   static void relay(const E& e)
   {
      std::cout << "No relay to catch event " << e.e << std::endl;
   }

};

template <>
struct Relay <void*>
{
   static void relay()
   { }
};

template <class W>
struct Rel
{
   typedef Relay< W > WWW;
};

struct EvtTableInit
{
protected:
   template <class T>
   struct functions
   {
      template <class E>
      static void relay(const E& e)
      {
         T::WWW::relay(e);
      }
   };
};

struct EvtTable : private EvtTableInit
{
private:
   typedef void (*FPA)(const EvtA& );
   typedef void (*FPB)(const EvtB& );
   typedef void (*FPC)(const EvtC& );
public:
   template <class T>
   EvtTable(T& )
   :
   pfa_(&functions<T>::relay),
   pfb_(&functions<T>::relay)
   pfc_(&functions<T>::relay)
   {}

   void relay ( const EvtA& e) const
      {
      pfa_( e);
      }
   void  relay ( const EvtB& e) const
      {
      pfb_( e);
      }
   void relay (const EvtC& e) const
      {
      pfc_( e);
      }

private:
   FPA pfa_;
   FPB pfb_;
   FPC pfc_;
};

template <class EVT_TABLE>
class RelayInterface
{
public:
   typedef Relay< void > WWW;
   template <class T>
   RelayInterface(T& x)
   : et_(x)
   {}
   template <class E>
   void relay(const E& e) const
   {
      et_.relay( e);
   }

private:
   EVT_TABLE et_;
};

template <class EVT_TABLE>
struct RelayInterfaceContainer
{
   template <class R>
      void addRelay()
      {
         Rel<R> w;
         RELAY r(w);
         vf_.push_back(r);
      }
   template <class E>
   void relay(const E& e)
   {
   for ( typename VF::iterator i = vf_.begin(); i != vf_.end(); ++i)
      {
      i->relay(e);
      }
   }
private:
   typedef RelayInterface<EVT_TABLE> RELAY;
   typedef std::vector <RELAY> VF;
   VF vf_;
};

void action()
{

   EvtB eb('b');
   EvtA ea('a');
   EvtC ec('c');

   typedef TopLayerDispatcher <
      BOLayerDispatcher <
         BRLayerDispatcher <
            void >
         >
      >
      W;

   typedef TopLayerDispatcher <
      BOLayerDispatcher <
         void >
      >
      WW;

   RelayInterfaceContainer<EvtTable> rc;

   rc.addRelay<W>();
   rc.addRelay<WW>();

   rc.relay(eb);

}

The events are now kept in the static interface, their use becoming implicit. As a result, RelayInterface can be refactored and simplified.

RelayInterface doesn't use the extra void* instance variable, because the whole approach is now static. Taking advantage of the unique signature of the relay, method EvtTableInit encapsulates the main "hiding" mechanism used by the static interface while EvtTable and EvtTable1 demonstrate how easily you can extend the model to allow new events to be integrated. On the same note, RelayInterfaceContainer takes EvtTable as a policy, further increasing the flexibility.

The previous versions were inflexible: There is a compilation error every time a handler for a certain event is missing. Now, this requirement was relaxed and the user can choose between two different specializations, triggered by selecting the appropriate end-of-chain tag:

  1. Relay<void> has a new catchall method that defers the catch of unsupported events for runtime.
  2. Relay<void*> preserves the previous behavior.

Where We Are

This time, I presented three type-safe variations of a single-layered Event Dispatching mechanism, using a Chain of Responsibility-like relaying mechanism. The basic implementation was refined to accommodate generalizations such as distributing the same event to more than one handler or different end-of-chain policies. The Static Interface idiom was used to make the code more generic.

In the next installment, I'll discuss a multilayered Event Dispatching implementation, together with an overview of potential use cases.

Download the Code

To download the code that accompanies this article, click here.

References

[1] Pattern Hatching: Design Patterns Applied by John M. Vlissides. Addison-Wesley, 1998.

[2] Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, and Ralph Johnson. Addison-Wesley, 1994.

[3] The code can be found in Basic1LayeredRelay/relay.h.

[4] C++ Templates: The Complete Guide by David Vandevoorde and Nicolai M. Josuttis. Addison-Wesley, 2002.

[5] The code can be found in 1LayeredRelayVer1/relay.h.

[6] "C++ Idioms in BREW: Better Interfaces."

[7] The code can be found in 1LayeredRelayVer2/relay.h





Page 2 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel