One of the tangent patterns associated with isolation or Single Responsibility is the Strategy Pattern. I use Castle Windsor as my IoC of choice and I had hoped there was some black magic built in to make the Strategy Pattern dead simple. Turns out there is and there isn’t.
What is the Strategy Pattern
The strategy pattern is a way of isolating what would often be found in a case statement. The bland example being calculating Sales tax on an Order. Each state and/or country has its own tax calculation and rather than
you should be breaking each calculation out into different ITaxStrategy implementations and consuming them like so:
The question remains, how to eliminate the conditional logic in TaxStrategyFactory determining which strategy is chosen. The solution is to set up a convention for discovering the available strategies and delegating the criteria to the Strategy itself.
The concept here is that we don’t have to “hook up” any new Tax Strategy to our Processing mechanism, we just “publish” new ones in a known way and they are picked up simply based on their existence. This is often referred to as Convention Over Configuration. We accomplish this with Castle Windsor with the Fluent Registration API:
This will register all classes found in the executing assembly which have the suffix “Strategy” as implementing the ITaxStrategy interface. The second half of convention based discovery is consuming the ITaxStrategy implementations. We can want to get all of these into the TaxStrategyFactory via a constructor array dependency:
Unfortunately, Windsor doesn’t support arrays as dependencies by default. Windsor has the concept of Resolvers (a strategy implementation itself) and we are going to have to tell it about the ArrayResolver which knows how to, err, resolve array dependencies:
Cool, so put these all together in our registration code and we get:
At this point we can kick of the true discovery chain of events through:
So in OrderProcessor.Process we are asked the TaxStrategyFactory for the applicable ITaxStrategy implementation:
The question is, how did the factory determine which of the boundless implementations applies? That’s the job of each ITaxStrategy itself. Here’s the implementation of our factory:
Notice how the TaxStrategyFactory depends on all of the ITaxStrategy implementations. It then uses the ITaxStrategy.IsApplicable(Order) method as criteria for a FirstOrDefault call. Here’s an example TaxStrategy for any Order with country “USA”:
You can download the sample project that goes along with this here.