IOC Containers - Part 3

Saturday, November 15, 2008

A little thinking after the last post lead me to the idea of persisting objects beyond the scope of a running program, a super singleton. Because an IOC container abstracts the source, and in some ways the lifespan, of an object, there is nothing stopping me from persisting the object and using it between application instances or other systems. The code would never have to know.

I used db4o in my code because of the simplicity, and native object storage. If you have never used db4o, I suggest it.

Also, this provider required explicit disposal, so I went back and implemented IDisposable on the Repository and the SingleServiceProvider.

Usage:

var r = new Repository();

r.RegisterServiceProvider(new SingleServiceProvider());

r.Register<IObjectContainer, SingleServiceProvider>(() => Db4oFactory.OpenFile("test.db"));
r.RegisterServiceProvider(new DB4OSingleServiceProvider(r.GetInstance<IObjectContainer>()));

r.Register<IMyClass, DB4OSingleServiceProvider>(() => new MyClass("a"));

IMyClass m = r.GetInstance<IMyClass>();

r.Dispose();

Code:

public class DB4OSingleServiceProvider : IRepositoryServiceProvider, IDisposable
{
private readonly Dictionary<Type, object> objects = new Dictionary<Type, object>();
private readonly IObjectContainer container;

public DB4OSingleServiceProvider(IObjectContainer container)
{
this.container = container;
}

~DB4OSingleServiceProvider() { this.Dispose(false); }

public void Remove(Type targetType)
{
var resultSet = this.container.Query(targetType);
while (resultSet.HasNext())
{
this.container.Delete(resultSet.Next());
}
this.container.Commit();
}

public void Update(object o)
{
this.container.Store(o);
}

#region IRepositoryServiceProvider Members

public void RegisterService(Type targetType, FactoryDelegate factoryMethod)
{
var resultSet = this.container.Query(targetType);
if (resultSet.Count < 1)
{
this.container.Store(factoryMethod());
}
}

#endregion

#region IServiceProvider Members

public object GetService(Type serviceType)
{
return this.container.Query(serviceType).Next();
}

#endregion

[ IDisposable Members … ]
}

I have not resolved how to handle events when the object has not been re-hydrated or dealing with multiple applications touching the same object database. Perhaps I will think about that some other time.

The biggest thing I have learned from this is that building something myself helps me understand. Now I am going to to try Autofac.

IOC Containers - Part 2

My initial IOC container was an automatic factory. When GetInstance was called it executed the stored factory method and returned. You could technically put anything you wanted into the factory method, including a singleton instance or a database call, but it is no better than a standard factory method.

While looking at some of the IOC containers listed in my last post, I noticed the user could select the lifespan of each type. So, I went back to the workbench.



I have abstracted out the lifespan from the repository, through the IRepositoryServiceProvider interface. This change also opens up the possibility for creating custom lifespans.

Usage:

var r = new Repository();
r.RegisterServiceProvider(new FactoryServiceProvider());
r.Register<IMyClass, FactoryServiceProvider>(() => new MyClass("a"));
IMyClass m = r.GetInstance<IMyClass>();

Code:

public delegate object FactoryDelegate();
public delegate T FactoryDelegate<T>();

public interface IRepositoryServiceProvider : IServiceProvider
{
void RegisterService(Type targetType, FactoryDelegate factoryMethod);
}

public class RepositoryPlusOne
{
private readonly Dictionary<Type, IRepositoryServiceProvider> providers =
new Dictionary<Type, IRepositoryServiceProvider>();
private readonly Dictionary<Type, IRepositoryServiceProvider> types =
new Dictionary<Type, IRepositoryServiceProvider>();

public void Register<T, U>(FactoryDelegate<T> factoryMethod)
where T : class
where U : IRepositoryServiceProvider
{
Type u = typeof(U);
Type t = typeof(T);

if (!providers.ContainsKey(u)) throw new ArgumentException("Unregistered Provider", "U");

providers[u].RegisterService(t, () => factoryMethod());
types.Add(t, providers[u]);
}

public void RegisterServiceProvider(IRepositoryServiceProvider provider)
{
providers.Add(provider.GetType(), provider);
}

public TService GetInstance<TService>() where TService : class
{
Type t = typeof(TService);
if (!this.factories.ContainsKey(t)) return default(TService);
return this.factories[t]() as TService;
}
}

public class FactoryServiceProvider : IRepositoryServiceProvider
{
private readonly Dictionary<Type, FactoryDelegate> factories =
new Dictionary<Type, FactoryDelegate>();

#region IRepositoryServiceProvider Members

public void RegisterService(Type targetType, FactoryDelegate factoryMethod)
{
factories.Add(targetType, factoryMethod);
}

#endregion

#region IServiceProvider Members

public object GetService(Type serviceType)
{
return (factories.ContainsKey(serviceType)) ? factories[serviceType]() : null;
}

#endregion
}

public class SingleServiceProvider : IRepositoryServiceProvider
{
private readonly Dictionary<Type, object> objects =
new Dictionary<Type, object>();

#region IRepositoryServiceProvider Members

public void RegisterService(Type targetType, FactoryDelegate factoryMethod)
{
objects.Add(targetType, factoryMethod());
}

#endregion

#region IServiceProvider Members

public object GetService(Type serviceType)
{
return (objects.ContainsKey(serviceType)) ? objects[serviceType] : null;
}

#endregion
}

IOC Containers

Friday, November 14, 2008

Inversion of control is an interesting pattern, but it has a flaw. When building a system following the IOC pattern, the code bloat seems to bubble up to the top. The higher level objects start creating and managing all the required parts. This starts to fall down when you scale it up beyond a small number of objects. The accepted solution is to move most of the object creation into an IOC container, like Windsor, StructureMap, Autofac, etc.

You register a type, and dependencies, with the IOC container and then just ask for it when you need it. That is great, except I don't know how or why they work the way they do. Most people have responded to my questions with the classic open source creed, “you can look at the source.” Reading the source is like reading a scientific research paper. So, I tried building one myself.

public delegate object FactoryDelegate();
public delegate T FactoryDelegate<T>();

public class Repository
{
private readonly Dictionary<Type, FactoryDelegate> factories =
new Dictionary<Type, FactoryDelegate>();

public void Register<T>(FactoryDelegate<T> factoryMethod) where T : class
{
this.factories.Add(typeof(T), () => factoryMethod());
}

public TService GetInstance<TService>() where TService : class
{
Type t = typeof(TService);
if (!this.factories.ContainsKey(t)) return default(TService);
return this.factories[t]() as TService;
}
}

After a few hours of tinkering, my basic IOC container is 16 lines, including error handling.

Usage:

var r = new Repository();
r.Register<IMyClass>(() => new MyClass("a"));
IMyClass m = r.GetInstance<IMyClass>();

A factory method is registered for each type you want the Repository to handle. Each time the GetInstance method is called, it retrieves the stored factory method, executes it, and returns a strongly typed result. I think I will explore this a little more.