Non invasive API - Now with an IoC container
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.