Non invasive API - Now with an IoC container

time to read 2 min | 376 words

I talked about ways to avoid invasive API design, and a lot of people asked about how to handle this with a container. First, I want to make it clear that the previous example assumed that you can't rely on a container, so it used Poor Man IoC to do that. Now that we can assume that there is a container, this is another matter entirely.

The API design now shifts to allow me to select the proper implementation from the container automatically. This generally ends up being either as a naming convention on top of a fixed interface, or as a generic interface with a given type as a parameter. The decision depends on what you are doing, basically, and the capabilities of your tools.

For myself, I strongly favor the generic interface approach, which would give us the following syntax:

public interface IMessageHandler<TMsg>
	where TMsg : IMessage
{
	void Handle(TMsg msg);
}

public class EndPoint
{
	public void HandleMsg(IMessage msg)
	{
		// this should be cached and turned into a delegate
		// not reflection call
		GetGenericHandleMsgMethod().MakeGenericMethod(msg.GetType())
			.Invoke(this, new object[]{msg});
	}

	public void HandleMsg<TMsg>(TMsg msg)
		where TMsg : IMessage
	{
		IoC.Resolve<IMessageHandler<TMsg>().Handle(msg);
	}

}

There is some ugliness in invoking the method with the generic parameter, but this allows you to handle a wide variety of cases very easily.

Let us take another example, this time it is from the Prism.Services.RegionManagerService:

public void SetRegion(DependencyObject containerElement, string regionName)
{
	IRegion region = null;

	if (containerElement is Panel)
	{
		region = new PanelRegion((Panel)containerElement);
	}
	else if (containerElement is ItemsControl)
	{
		region = new ItemsControlRegion((ItemsControl)containerElement);
	}

	if (region != null)
		_regions.Add(regionName, region);
}

I don't really like this code, let us see what happens when we introduce the idea of the container as a deeply rooted concept:

// only use for selection
public interface IRegion<T> : IRegion {}

public void SetRegion(DependencyObject containerElement, string regionName)
{
	IRegion region = (IRegion) IoC.TryResolve(typeof(IRegion<>).MakeGenericType(containerElement.GetType()));

	if (region != null)
		_regions.Add(regionName, region);
}

Now the code is much clearer, we can extend it from the outside, without modifying anything when we add a new region type.