Believe it or not, the following code represents most of LinFu.IoC's functionality when managing service instances:
public class SimpleContainer : IContainer
{
private readonly Dictionary<type,> _factories = new Dictionary<type,>();
public virtual bool SuppressErrors
{
get; set;
}
public virtual void AddFactory(Type serviceType, IFactory factory)
{
_factories[serviceType] = factory;
}
public virtual bool Contains(Type serviceType)
{
return _factories.ContainsKey(serviceType);
}
public virtual object GetService(Type serviceType)
{
object result = null;
if (!_factories.ContainsKey(serviceType) && !SuppressErrors)
throw new ServiceNotFoundException(serviceType);
if (!_factories.ContainsKey(serviceType) && SuppressErrors)
return null;
var factory = _factories[serviceType];
if (factory != null)
result = factory.CreateInstance(this);
return result;
}
}
public class NamedContainer : SimpleContainer, INamedContainer
{
protected readonly Dictionary<string,>> _namedFactories =
new Dictionary<string,>>();
public virtual void AddFactory(string serviceName,
Type serviceType, IFactory factory)
{
if (serviceName == string.Empty)
{
AddFactory(serviceType, factory);
return;
}
if (!_namedFactories.ContainsKey(serviceName))
_namedFactories[serviceName] = new Dictionary<type,>();
_namedFactories[serviceName][serviceType] = factory;
}
public virtual bool Contains(string serviceName, Type serviceType)
{
if (serviceName == string.Empty)
return Contains(serviceType);
return _namedFactories.ContainsKey(serviceName) &&
_namedFactories[serviceName].ContainsKey(serviceType);
}
public virtual object GetService(string serviceName, Type serviceType)
{
if (serviceName == string.Empty)
return GetService(serviceType);
bool exists = Contains(serviceName, serviceType);
if (!exists && SuppressErrors)
return null;
if (!exists && SuppressErrors != true)
throw new NamedServiceNotFoundException(serviceName, serviceType);
var factory = _namedFactories[serviceName][serviceType];
if (factory == null)
return null;
return factory.CreateInstance(this);
}
}
As you can see, there's nothing special about this code. Some might even scoff at it since it's too minimalistic to be useful. After all, one might
say that an IoC container has far more responsibilities than just object instantiation. Ninject, for example has features
such as contextual binding and method interception. Surely this isn't all there is to LinFu.IoC's features, is it?
Bend it like Ockham
Despite the varying complexity of most (if not all) IoC containers in the field, logically speaking, the code listed above is the absolute minimum
amount of code necessary to separate the instantiation of a service instance from its actual client code. When you request a service instance from a given
IoC container (whether it be LinFu, Ninject or countless other containers out there), there has to be some point where the container has to decide if that
service instance can be created, as well as manage the lifetime of that service once it is already out of the container. As most of us IoC container developers know,
there's quite a lot more to a container than just managing the lifetime of its individual services. Creating the instance is only the first step,
and I'll show you how LinFu version 2's new IoC container will implement the some of the same features without sacrificing its relatively-horizontal learning curve.
The Pattern of Other Containers
Typically, these containers will use either property setter injection or constructor injection to autowire together all of the dependencies that an application
might need during its lifetime. In addition, they might implement additional features such as interception, logging, and AOP. However, despite the differences
in features among containers, there are two points where these features are commonly applied:
- When a service is about to be created (e.g. constructor injection) or,
- When a service is already instantiated (e.g. property setter injection, or interception)
This implies that a developer can add additional features to their respective IoC container simply by controlling the point where the service
is going to be created as well as controlling the point where the service has been recently instantiated. In fact, if you can isolate those two points
from the rest of the container, you can effectively add new features without affecting the rest of the code.
Now the reason why LinFu's IoC container can get away with such simple code is because it actually delegates its factory methods to an instance of the IFactory interface:
public interface IFactory
{
object CreateInstance(IContainer container);
}
LinFu's IoC container uses each factory instance to determine how a service implementation should be instantiated, and each factory instance, in turn,
is responsible for managing the lifetime of each component that it creates. With that in mind, the only thing that you have to do to extend LinFu's IoC container
is to control how each factory creates its object instances (such as deciding which constructor and constructor arguments to use when implementing constructor
injection) and control the instances that come out of each factory.
For example, if I wanted to add interception to LinFu.IoC, all I have to do is wrap
a Decorator around an IContainer (or INamedContainer) instance that wraps each service instance in a proxy:
public class ContainerDecorator : IContainer
{
private IContainer _container;
public ContainerDecorator(IContainer realContainer)
{
_container = realContainer;
}
public object GetService(Type serviceType);
{
var result = container.GetService(serviceType);
if (result != null)
return SomeProxyFactory.Wrap(result);
return result;
}
}
...and in the client code, using the additional decorator is as easy as:
var container = new ContainerDecorator(new SimpleContainer());
var service = container.GetService<ISomeServiceType>();
As you can see, the implementation of LinFu.IoC is very straightforward, and there's really nothing exotic about the design.
For those of you who were probably wondering why LinFu.IoC isn't using generics, here's the list of extension methods that completes the design:
public static class ContainerExtensions
{
public static T GetService<t>(this IContainer container)
where T : class
{
var serviceType = typeof (T);
return container.GetService(serviceType) as T;
}
public static T GetService<t>(this INamedContainer
container, string serviceName)
where T : class
{
return container.GetService(serviceName, typeof (T)) as T;
}
public static void AddFactory<t>(this INamedContainer container,
string serviceName, IFactory<t> factory)
{
IFactory adapter = new FactoryAdapter<t>(factory);
container.AddFactory(serviceName, typeof (T), adapter);
}
public static void AddFactory<t>(this IContainer container,
IFactory<t> factory)
{
IFactory adapter = new FactoryAdapter<t>(factory);
container.AddFactory(typeof(T), adapter);
}
public static void AddService<t>(this IContainer container, T instance)
{
container.AddFactory(typeof (T), new InstanceFactory(instance));
}
}
Again, there's nothing unconventional in the design. In the end, it's the simplicity that matters most, and that is the difference that LinFu.IoC offers.