Using a Container To Make a Better Factory
Lately I have been delegating a lot to containers. If you consider what containers can provide, there are a lot of ways you can leverage them. Let’s consider the case where you have a Factory implemented using a traditional Factory Pattern to create concrete instances of an abstract type like an interface or base class.
The most common use of the factory pattern involves a switch statement where you say something like “Switch key Case A return new TypeA()”. Factories are a great way to defer object creation but part of the problem with a factory is that it is very much coupled to the objects it creates and the values used to determine which object to create. This can be considered as acceptable since you know the factory has a dirty job. However, let’s consider how we can eliminate the need for switch statements in a factory and let the container do the work.
Most containers support adding objects with a name or key; commonly referred to as a named instance. This behavior is what we will leverage to make a “better factory.”
Consider this traditional factory (Figure 1)
public class WidgetRepositoryFactory { public IWidgetRepository Create(string repositoryKey) { switch (repositoryKey.ToLower()) { case "xml" : return new WidgetXmlRepository(); case "csv": return new WidgetFileRepository(); default : throw new ArgumentException("Invalid repositoryKey"); } } }
Nothing fancy. Our traditional factory can provide either an Xml Repository or File Repository. The repositoryKey could come from a configuration value or even a parameter to some type of request or user input.
Now let’s take a look at a “better factory”. This example will use StructureMap as the container of choice.
Note: What this example doesn’t illustrate is how our container bootstraps the application. To focus on the better factory concept just assume a valid container is populated and provided to the factory when needed.
Consider our better factory (Figure 2)
public class WidgetRepositoryBetterFactory { private readonly IContainerProvider _containerProvider; public WidgetRepositoryBetterFactory(IContainerProvider containerProvider) { _containerProvider = containerProvider; } public IWidgetRepository Create(string repositoryKey) { return (IWidgetRepository) _containerProvider.Resolve <IWidgetRepository>(repositoryKey); } }
Within the Create method we are simply using our container to resolve an instance of the IWidgetRepository by it’s key. This is a bare bones implementation and is not providing any sanity check on the arguments. There are a couple ways you could go about that, so I’ll leave it up to your imagination.
The “better factory” is wired up to take an instance of an IContainerProvider which looks like the following:
public interface IContainerProvider { object Resolve<T>(); object Resolve<T>(string key); }
We can wire this up in Structure Map like so:
public StructureMapProvider() { _container = new Container(); _container.Configure(x => { x.ForRequestedType<IContainerProvider>() .TheDefault.IsThis(this); x.ForRequestedType<IWidgetRepository>().AddInstances (i => i.OfConcreteType<WidgetFileRepository>() .WithName("csv")); x.ForRequestedType<IWidgetRepository>().AddInstances (i => i.OfConcreteType<WidgetXmlRepository>() .WithName("xml")); }); }
When someone calls our StructureMapProvider’s Resolve<T> method for IContainerProvider, they will be given that instance of the StructureMapProvider. This can be a little confusing at first, but in cases where you do not want your container to be created multiple times, this allows you to to set up your container to support that.
The important lines to review in the StructureMap configuration are these two:
x.ForRequestedType<IWidgetRepository>().AddInstances (i => i.OfConcreteType<WidgetFileRepository>().WithName("csv")); x.ForRequestedType<IWidgetRepository>().AddInstances (i => i.OfConcreteType<WidgetXmlRepository>().WithName("xml"));
As of StructureMap 2.5.2 this is how you can register named instances within the Configure expression. What I think is a little misleading is that this sounds as though you are creating this as a Singleton by saying “Add an instance of this type, with this name.” In my testing I found that the instance returned when resolving one of these types is a new instance. The grammar of the API can be a bit tricky if you are not fully-versed in it.
Containers certainly offer a lot of power and you can delegate a lot to a container. Hopefully next time you need to leverage a factory you can consider how to make it a “better factory”™. Enjoy!
Comments are closed.

