The basic interfaces and classes are in place to implement the Visitor design pattern. Now for combining Visitor with Observer. We will create a MessageDispatcher class that implements the IMessageVisitor interface. It will provide functionality for raising events for each of the message classes it visits:
The purpose of the MessageDispatch class is simple; it raises events in response to visiting message objects.
The beauty of this class is that it can be reused in many different contexts. In fact, it may turn out that this is the only visitor class we need. Instead of creating many implementations of the IMessageVisitor interface, we can use this class instead and register with the events to be notified when the visitor visits a message type we are interested in. I'll build on this idea in another post when I explore flow-based programming.
Before I show the MessageDispatch class in action, let's now combine Visitor/Observer with Iterator. I'll use C# v2.0 iterators.
Say we have a class that has a collection of IMessage objects. We'll give it an iterator for iterating over the collection, returning the index of each object in the collection, and visiting each object in the collection:
private List<IMessage> messages = new List<IMessage>();
// Other class declarations, methods, properties, etc...public IEnumerable<int> Iterator(IMessageVisitor visitor)
int index = 0;
foreach(IMessage message in messages)
Here, all we're doing is returning the collection index of each message before visiting the message. It isn't very impressive, but this is a very generic and simple example. In other situations, what your iterator returns can be just about anything, some sort of on the fly calculation, or whatever. Anything that is relevant to the traversal. And you can create several interators for your class that return different values and have different traversal strategies.
The position of the yield return statement is important and requires some thought. When the yield return is executed, the iterator returns. Viewed from the outside, this return causes the MoveNext call to complete. The rest of the code that comes after the yield return is not executed until MoveNext is called again. When used with Visitor, this is important to consider.
If you need to use the value stored in the Current property at the conclusion of a MoveNextbefore you visit the current object, it's important to put the yield return before the object is visited; otherwise, you may want to visit the object first.
C# Iterators are a little hard to reason about because of the jump that can happen in the middle of the iterator's code. In fact, you can have several yield returns throughout the iterator. It becomes tricky, and one is reminded of gotos, but at least so far, I'm finding iterators to be very powerful.
Now let's look at all of this in action:
private MessageCollection messages = new MessageCollection();
private MessageDispatcher dispatcher = new MessageDispatcher();
dispatcher.MessageAOccurred += EventHandler(HandleMessageA);
// Other class declarations, methods, properties, etc...privatevoid HandleMessageA(object sender, EventArgs e)
// Do something here.
foreach(int index in messages.Iterator(dispatcher))
// Do something with index here.
This admittedly doesn't look impressive. But one advantage that's apparent even from this simple example is that our Program class doesn't have to implement the IMessageVisitor class to visit the messages. The MessageDispatcher does all of the work.
Using events in this way may seem like overkill, and if it ended with our Program class, I would agree. The real power comes in when more than one class responds to the events generated by the Visitor/Observer class. You can design classes to respond to the events raised by the Visitor/Observer class that has no knowledge of the mechanics going on behind the scenes. All they are interested in is doing something interesting with the objects being visited.
To give a more concrete example, I'm using the above approach with my MIDI toolkit. I've rewritten the playback engine to use Visitor/Observer/Iterator. Various compenents are connected to my Visitor/Observer class to process and react to MIDI events. For example, I have a clock class that responds to tempo events raised by the Visitor/Observer by changing its tempo. Also, the iterators are driven by the ticks the clock generates. It's all working well so far and has made my code more expressive.
Well, that's about it. I may return to this idea and use the above as the basis for an article at some point. That's for your time.
Last Visit: 31-Dec-99 19:00 Last Update: 13-Dec-17 1:21