Here's a more complex but pretty flexible approach. We'll define an interface
for a type that can draw some events:
interface IEventRenderer
{
// Draw the given event, if it can. Return true if the event was drawn,
// false otherwise.
bool Draw(Event event);
}
It does two things: it checks to see if it can draw a given event, and, if so,
draws it. Otherwise it bails and returns false.
For example, a class that can render Sport1Events looks like:
class Sport1EventRenderer : IEventRenderer
{
public bool Draw(Event event)
{
var sportEvent = event as Sport1Event;
// can only draw this type
if (sportEvent == null) return false;
// draw the event...
return true;
}
}
Then we'll define a registry class. It's job is to maintain a collection of
these renderers and hand off the work of drawing an event to the appropriate
one:
class EventRendererRegistry
{
public void Add(IEventRenderer renderer)
{
mRenderers.Add(renderer);
}
public void Draw(Event event)
{
foreach (var renderer in mRenderers)
{
if (renderer.Draw(event)) break;
}
}
private readonly List<IEventRenderer> mRenderers = new List<IEventRenderer>();
}
All it does is find the first renderer that can successfully draw the event.
You would then use this like:
var registry = new EventRendererRegistry();
registry.Add(new Sport1EventRenderer());
registry.Draw(someEvent);
Pros:
- Event types are not coupled to any rendering code.
- Renderers are not coupled to each other.
- Renderers are only coupled to the events they care about. (For example a
Sport2EventRenderer would not need to be coupled to Sport1Event.)
- Renders can do arbitrary logic to determine if they're appropriate. We're just
doing a type test here, but we could see if the event implements a certain
interface, has a certain property, is in a certain state, etc.
- Relatively fast. No reflection beyond simple casting.
Cons:
- Fairly complex.
- Can fail at runtime to find a matching renderer.
- Have to iterate through renderer collection each time to find a match.