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.

Comments
Comment preview