Friday, June 1, 2012

Using a Func-y Dictionary Instead of a Switch

Recently I felt that the Strategy Pattern was appropriate for solving a problem. I thought that a switch statement would discriminate between the different strategies but I had reservations. I knew that the length of the switch statement would grow and become an eyesore. I had been using F# recently and had been really impressed with the succinctness of the language. These factors inspired me to find a more elegant solution to my problem.

The selection logic for each Concrete Strategy was simply to match a string value exposed as a property of a POCO. The result for each test was boolean, and a value of true indicated that a particular Concrete Strategy was to be used. I needed to somehow correlate this boolean test with an instantiated object. It occurred to me that a Dictionary<TKey, TValue> seemed to fulfill the need for this type of lookup. (Because I didn't want to unnecessarily instantiate objects, I threw Lazy<T> into the mix.)

Here is how the Dictionary is loaded:
Dictionary<Func<ITestedObject, bool>, Lazy<IStrategy>> lookup = new Dictionary<Func<ITestedObject, bool>, Lazy<IStrategy>>();

lookup.Add(o => o.Identifier.Equals("ABC-123", StringComparison.OrdinalIgnoreCase), new Lazy<IStrategy>(() => new ConcreteStrategy0()));
lookup.Add(o => o.Identifier.Equals("ABC-456", StringComparison.OrdinalIgnoreCase), new Lazy<IStrategy>(() => new ConcreteStrategy1()));
lookup.Add(o => o.Identifier.Equals("ABC-789", StringComparison.OrdinalIgnoreCase), new Lazy<IStrategy>(() => new ConcreteStrategy2()));

Here is how I use it. In my case I need to test whether members of an IEnumerable<ITestedObject> all have the same Identifier value. If all of the tested items have the same Identifier, which is determined by the use of the Dictionary's key as the predicate in the 'All' call, then I get the Value from the appropriate Dictionary entry. (Note that the object returned is Lazy<IStrategy>, and I'll have to get the actual Concrete Strategy through a call to Lazy<T>.Value.) Specifically:
var concreteStrategy = lookup.FirstOrDefault(d => someResult.TestedObjects.All(d.Key)).Value;

There is certainly room for refinement and improvement, but this solution worked well for me.