Messaging Pattern

Started by
4 comments, last by Matt Green 16 years, 6 months ago
I'm looking for a pattern which would allow me to send messages to objects while respecting the open/closed principle. I want to have two base classes, Component and Message, and the Component has a pure virtual method which accepts a Message argument with the expected behaviour (for its derived classes) that the method either processes the message (if it recognizes it) or throws an exception. The interface and implementation of these classes should be set in stone once and forever. Then, using these, I should be able to defined a ConcreteMessage, and a ConcreteComponent which is able to recognise and process ConcreteMessage. This bears some resemblance to both visitor and command design patterns, but both require that the Component and Message base clases have some knowledge about the ConcreteComponent and ConcreteMessage derived classes, which is something that would violate the open-closed principle for my «Component + Message» module.
Advertisement
Some questions to put the problem in context.
1) What do you plan to do with Message objects that you cannot do with plain method calls? I'm not saying you don't need objects, only that the purpose of the reification is unexplained.
2) Why do you assume that Component and Message themselves know about their own and each other's implementations?
If you are implying the sort of hardwired double dispatch of traditional Visitor styles, it isn't appropriate: Component subclasses will receive only appropriate Message subclasses and vice versa, with no need for double dispatch: either object can try to cast the other to the appropriate interface and if the cast doesn't succeed the message has been routed wrongly.
3) What are the planned hierarchies of Component and Message specializations and implementations?

Omae Wa Mou Shindeiru

Quote:Original post by LorenzoGatti
Some questions to put the problem in context.
1) What do you plan to do with Message objects that you cannot do with plain method calls? I'm not saying you don't need objects, only that the purpose of the reification is unexplained.


Messages are serializable, and can be sent over a network to components which live there. The idea being to send messages without caring whether the addressee is local or remote.

Quote:2) Why do you assume that Component and Message themselves know about their own and each other's implementations?


I don't. The point is exactly that they shouldn't.

Quote:3) What are the planned hierarchies of Component and Message specializations and implementations?


I honestly have no idea. Perhaps "set position", "set direction", "lose health", messages, "character", "object", "projectile" components.

I'm assuming the language is c++.

This is hard to do in a language that do not expose meta-information about classes and methods.

I think this is often implemented by simulating double-dispatch with the visitor pattern. But that do not confine that well to the open/closed-principle since you have to add code that the visitor each time you add a new ConcreteMessage.

Interesting question though. I will be polling this thread for new aspects of this problem.

Lizard
Quote:Original post by ToohrVyk
Quote:Original post by LorenzoGatti
3) What are the planned hierarchies of Component and Message specializations and implementations?


I honestly have no idea. Perhaps "set position", "set direction", "lose health", messages, "character", "object", "projectile" components.


Let's suppose there is a Character component compatible with, among other types, ChangeHealth and SetWaypoint messages.
You could make these messages subclasses of CharacterMessage, in turn a subclass of Message, and there can be a generic Message::execute(Component) virtual method which CharacterMessage can specialize to a template method that
1) downcasts the Component to a Character (or fails)
2) calls the abstract CharacterMessage::execute(Character), which sees the specific data of concrete messages. and a Character instance. For example, ChangeHealth can directly call Character.setHealth(int).

Of course there is another style for the same thing: subclasses of Message<Character> overriding Message<Character>::execute(Character), without downcasting and without virtual functions.
.

Omae Wa Mou Shindeiru

Maybe I can be of some help.

Event.hpp:
template<typename DerivedEvent>struct Event{	template<typename Class>	static EventBinding Bind(Class* instance, void (Class::* function)(DerivedEvent&), EventHandlerPriority priority = Normal)	{		const wchar_t* name = DerivedEvent::GetName();		EventHandler handler(instance, function, priority);				EventBind(name, handler);		return EventBinding(name, handler);	}	static EventBinding Bind(void (*function)(DerivedEvent&), EventHandlerPriority priority = Normal)	{		const wchar_t* name = DerivedEvent::GetName();		EventHandler handler(function, priority);				EventBind(name, handler);		return EventBinding(name, handler);	}	void Invoke()	{		EventInvoke(DerivedEvent::GetName(), static_cast<DerivedEvent*>(this), DoInvokeHandler);	}	template<typename Class>	static void Unbind(Class* instance, void (Class::* function)(DerivedEvent&))	{		EventUnbind(DerivedEvent::GetName(), EventHandler(instance, function));	}	static void Unbind(void (*function)(DerivedEvent&))	{		EventUnbind(DerivedEvent::GetName(), EventHandler(function));	}private:	static void DoInvokeHandler(void* event, const EventHandler& handler)	{		Delegate<void (DerivedEvent&)> target;		target.SetMemento(handler.Target);		target(*static_cast<DerivedEvent*>(event));	}};


I wanted client code to look like this:
ShutdownEvent::Bind(this, &ClientComponent::OnShutdown);// ...void ShutdownEvent::Bind(ShutdownEvent& e){}

It is straight-forward and easy to use. No down-casting or other silliness. And I was willing to move heaven and earth in order to accomplish that.

There were a few issues I encountered. The first one is that I wanted to keep a master list of event handlers in one place in the engine, despite the fact that every event handler would have a different signature (the first argument would be the event they are interested in) and wouldn't have a common base class. (Now, I could require that every implementor of subclassed events have their own list of handlers, but I remained stubbornly optimistic and dismissed this as less elegant.) It was important to me that the client code did not downcast the event when it received it, too. I wanted no unnecessary code, no noise required to get down to business. (I am extremely lazy, as you will find out.) Thus, the master list of event handlers would require subverting the type system a bit. After all, you can't exactly store function pointers without calling out their exact argument types.

Well, that is where I was wrong. After I learned way too much about function pointers, I stumbled upon the FastDelegate library. It provides a GetMemento() function that returns an opaque representation of the delegate...be it a function call, or a method call. So I had a way to obscure the physical type of the object without polymorphism and the associated dynamic allocation requirements. I could store the DelegateMemento type in my handler list. So inside of the engine, I keep a hash table of event names (ShutdownEvent) to a prioritized list of event handlers.

This led me to my second problem: how do I resurrect the type information? I just went to all that trouble to throw it away, and only then realized the real value of having it around - it makes the data usable! This point stumped me endlessly until I read about the Curiously Recurring Template pattern. Some time later I realized it was possible to use CRTP to bring the type information back. Look at the definition of the Invoke() method. It calls the engine's Invoke function, passing down the event name, a downcasted version of the this pointer, and a function pointer used to call the handler. The engine calls DoInvokeHandler once for every event handler that is registered. The SetMemento method takes the opaque method call and makes it usuable again. We use the type information that CRTP gives us to synthesize the correct type information for the function call and the argument. Finally, we call the function.

This was easily the most intense C++ hackery I'd ever done. I have not profiled it extensively. You're guaranteed that there is at least one indirect method call - the call through the delegate. How the DoInvokeHandler call is handled is up to your compiler - I'm almost certain it would also be indirect, but I have not taken the time to examine this. But I am more than fine with the performance of it, given the amazing amount of readability it confers on the code.

I am considering releasing it as a library of header files for people to examine and work with. It is close to what you are looking for Toohr, but I wrote this lengthy discussion of it to get the ball rolling in your head so you can continue refining it to your needs. I've only scratched the surface.

This topic is closed to new replies.

Advertisement