As I expected my previous post prompted a few questions regarding the Three Container Calls pattern I outlined. Major one of them is how to handle the following scenario:
We create our container, install all the components in it. Moments later we resolve the root component, container goes and creates its entire tree of dependencies for us, does all the configuration and other bookkeeping, injects all the dependencies and gives us back the new fresh ready to use objects graph that we then use to start off the application.
More often than not, this will not be enough though. At some point later in the lifecycle of the application, a user comes in and wants something from the application. Lets say the user clicks a button saying “Open a file” and at this point a “OpenFile” view should be loaded along with all of its dependencies and presented to the user. But wait – it gets worse – Because now if a user selects a .txt file a “DisplayText” view should be loaded, but when a .png file gets selected, we should load a “DisplayImage” view.
Generalizing – how to handle situations where you need to request some dependencies unavailable at registration or root resolution time, potentially passing some parameters to the component to be resolved, or some contextual information affecting which component will be resolved (or both).
Factory alive and kicking
The way to tackle this is to use a very old design patterns (from the original GOF patterns book) called Abstract factory and Factory method.
Nitpickers' corners – as I know I will be called out by some purists that what I’m describing here is not purely this or that pattern according to this or that definition – I don’t care, so don’t bother. What’s important is the concept.
It gives us exactly what we need – provides us with some components on demand, allowing us to pass some inputs in, and on the same time encapsulating and abstracting how the object is constructed. The encapsulating part means that I totally don’t need to care about how, and where the components get created – it’s completely offloaded to the container. The abstracting part means that I can still adhere to the Matrix Principle – the
spoon container does not exist.
How does this work?
Using Castle Windsor, this is very simple – all you have to do is to declare the factory interface, and Windsor itself will provide the implementation. It is very important. Your interface lives in your domain assembly, and knows nothing about Windsor. The implementation is a detail, which depends on the abstraction you provided, not the other way around. This also means that most of the time all of the work you have to do is simply declaring the interface, and leave all the heavy lifting to Windsor, which has some pretty smart defaults that it will use to figure this stuff out by itself. For non-standard cases, like deciding which component to load based on the extension of the file provided, you can easily extend/customize the way factory works by using custom selector. With well thought out naming convention, customizing Windsor to support this scenario is literally one line of meaningful code.
In addition to that (although I prefer the interface-driven approach) Windsor (upcoming version 2.5), and some other containers, like Autofac (which first implemented the concept), and StructureMap (don’t really know about Ninject, perhaps someone can clarify this in the comments) support delegate based factories, where just the fact of having a dependency on Func<IFoo> is enough for the container to figure out that it should inject for that dependency a delegate that when invoked will call back to the container to resolve the IFoo service. The container will even implement the delegate for you, and it also obviously means that the code you have has no idea (nor does it care) that the actual dependency was created by a container.
This is the beauty of this approach – it has all the pros and none of the cons of Service Locator, and shift towards supporting it was one of the major changes in the .NET IoC backyard in the last couple of months, so if you are still using Service Locator you’re doing it wrong.
06-22-2010 1:52 PM