Let me set the scene for the problem. I have a problem where I am writing a client library that will be used to call a web service. The web service will return an enumerable collection of objects. These objects will all derive from a common base type (we'll call it BaseType for want of a anything less descriptive) and each type will need to be processed in a different way. I know that the web service will return only three types at the time I write the client library, but in the future more types will be introduced.
The client library will be used by a large number of applications, so to reduce the need to redeploy these applications when the extra types are introduced, the library must be able to dynamically extend its type handling capabilities.
My first thoughts went straight to a loop through the collection, calling a method on each item to handle that item. Basic polymorphism. However, to do this the base type must expose the method and each derived type override it, neither of which I the case.
Next I thought about extension methods. The loop would look the same
public void HandleValues(IEnumerable<BaseType> values)
{
foreach (var item in values)
{
item.Handle();
}
}
but the extension methods would differ based upon type.
public static class ExtensionMethods
{
public static void Handle(this DerivedType1 item)
{
//do the work
}
public static void Handle(this DerivedType2 item)
{
//do the work
}
}
This does not work however, the type of item in the loop is BaseType, so no extension method is found.
A big nasty if block or switch statement could be introduced to either cast the item or call the correct handler method. But this is not very extensible, and certainly doesn't satisfy the requirement to be able to extend the set of types that can be handled without cracking open the code, recompiling and redeploying.
That is when dependency injection patterns came to mind. The idea is that an extension method is created to extend BaseType which will dynamically select a handler and call it.
public static void Handle(this BaseType item)
{
var handler = DIContainer.Instance.GetType<IHandler>(item.GetType().ToString());
handler.Handle(item);
}
and the loop is unchanged
public void HandleValues(IEnumerable<BaseType> values)
{
foreach (var item in values)
{
item.Handle();
}
}
The container I chose was MEF, as it allows very simple plugin extension. I define the handlers I a class library,drop this into a folder and simply point the MEF container to that folder to load all handlers. If i want to handle a new type, simply create the handler in a new assembly, drop the assembly into the correct folder, and the next time the app starts it will have the handler.
To load the correct handler requires that each handler is attributed with the type it is to handle. Also, all handlers must either derive from a common type, or implement a common interface, I chose the second
public interface IHandler
{
void Handle(BaseType item);
}
public interface IHandler<T>: IHandler where T:BaseType
{
void Handle(T item);
}
public abstract class BaseHandler<T> : IHandler<T> where T : BaseType
{
public void Handle(BaseType item)
{
Handle((T)item);
}
public abstract void Handle(T item);
}
This takes things a little further, the generic interface IHandler<T> has a method that takes an object of the derived type,but this cannot be called directly as we only have an object reference by the BaseType. As such I created a non generic interface with a method that takes a BaseType as it parameter, then I created an abstract BaseHandler class that implements both the generic andnon generic interfaces. The implementation of the non generic method casts the item to the derived type,which it knows by virtue of its generic definition, then calls the generic method with the derived type instance. This approach allow me to create concrete handlers that dont need to performany casting and can deal directly with instances of the derived type they are designed to handle.
an example of a handler would be
public class DerivedType1Handler : BaseHandler<DerivedType1>
{
public override void Handle(DerivedType1 item)
{
//do the work
}
}
While I have chosen MEF to implement the dynamic loading of the handlers and the selection of these, any number of other DI containers could be used. The use of MEF allows me to simply decorate the handlers with the type that they are to handle as their exported contract name:
[Export(contractName:"MyNamespace.DerivedType1", contractType:typeof(IHandler)]
public class DerivedType1Handler : BaseHandler<DerivedType1>
{
public override void Handle(DerivedType1 item)
{
//do the work
}
}
No comments:
Post a Comment