On the project I am currently working on we need to implement various processes consisting of a series of steps.
We have created some reusable classes based on the command pattern.
Using these classes we can implement very complex processes in a very readable way.
Below is an example of how we would implement a beer brewer (no my client is not a brewery, this is just an example and everyone loves beer so why not):
public class BrewingProcess
{
public void Run(BrewingContext context)
{
new Invoker(context)
.WithStep<AddMaltAndHops>()
.WithStep<Boil>()
.WithStep<Cool>()
.WithStep<Ferment>()
.WithStep<Bottle>()
.Run();
}
}
We have an invoker which is initialized with a context that will be passed to each step. This is the state of the process.
The WithStep will create each step and store it in the invoker.
Finally Run will invoke the Execute method of each of the steps.
This works great and the code is really readable. There is only one problem, the WithStep method need to construct an object of the type passed in.
Clearly the Invoker should not have knowledge about the concrete commands/step implementations.
Initially we dealt with this using reflection on the type on the generic method.
Injecting dependencies
This was working just fine for some time, untill one day one of our developers needed to create a step that had dependencies on other objects.
As the reflection required steps that could be constructed using the parameterless default constructor, we could not use constructor injection to inject these dependencies.
Our first solution was to use an overload on the Invoker that accepted a factory to create the steps. This way we could replace the default reflection based factory with something more sophisticated that would let us use a IoC container to resolve all the dependencies.
The developer created a factory like this which could be injected into the Invoker which in turn could use it to create commands:
public class CommandFactory<T> : ICommandFactory<T>
{
public ICommand<T> Create(Type type)
{
var command = IoC.Resolve(type) as IComand<T>;
return command;
}
}
Service Locator anti-pattern
But the developer quickly realized that he now had a call to the Resolve method in his code, and he immediately recognized this as the Service Locator pattern.
As he was not sure how to fix this, he asked the team for ideas.
Typed Factory Facility
One clever developer suggested that he should use Castle Windsor Typed Factory Facility as the container we are using is Castle Windsor.
The Typed Factory Facility is a very powerful feature and used correctly it can be very helpful. In this case we are creating a factory that can create any Command type that inherits from ICommand
Recognizing Service Locators
Mark Seeman the author of Dependency in .NET has a good post on how to Recognize a Service Locator
If we step back to the WithStep method of the invoker. We see that it closely matches the definition of a Service Locator.
public interface IServiceLocator
{
T Create<T>(object context);
}
Although the WithStep<T> method does not return, the created T (it stores it in a list) the effect is the same.
Simple Constructor Injection to the rescue
So given our current Invoker there is no solution to the problem.
Luckily there is a solution, we just need to step even one more step back and tweek our Invoker a bit.
Our BrewingProcess is dependent on each of the steps of the process.
It needs AddMaltAndHops, Boil, Cool, Ferment and Bottle steps to do its job, i.e. these are dependencies of the BrewingProcess.
And what do we do with required dependencies of a class? We inject them in the constructor of course.
public class BrewingProcess
{
private IEnumerable<ICommand<BrewingContext>> _steps;
public BrewingProcess(
AddMaltAndHops step1,
Boil step2,
Cool step3,
Ferment step4,
Bottle step5
)
{
_steps = new ICommand<BrewingContext>[] {
step1,
step2,
step3,
step4,
step5
};
}
public void Run(BrewingContext context)
{
new Invoker<BrewingContext>(context)
.WithSteps(_steps)
.Run();
}
}
What we needed to realize was that the invoker does not need to control the lifetime of the steps in the process. As long as the steps are created before we call the Run method on the Invoker we are happy.
What this buys us is that we let the IoC container do all the heavy lifting of building our dependencies and their dependencies. Furthermore we let the container worry about the lifetime of these objects. If we use the Typed Factory Facility we have to worry about managing the lifetime of the objects that we create. We need to make sure that we release created objects if needed and worry about when and if these objects need to be released.
If we don't really need to do that we should not do it.
Possible catch
One possible catch with the above solution is that if the steps store any state, and we call the Run method multiple times on the same instance, then we may end up in trouble. This is because the lifetime of the steps are now linked to the Process. But I think handling this possible problem is far easier than any of the alternatives I can think of.