Posted on Saturday September 2013
I have been abusing the strategy pattern of late, and ended up writing a StrategyResolver class.
This sort of thing is probably best left to a DI container, using a factory to resolve. But anyways...
We can inject a StrategyResolver
This lets use resolve the strategy through data. For example, I've been doing a bit web scraping and manipulation, so using this technique I can resolve a strategy based on the domain of the page I am processing.
I guess I'm really just injecting a factory. Sort of.
// create a resolver (or inject one)
StrategyResolver _strategyResolver = new StrategyResolver();
// use it to retrieve an implementation based on some key
_myStrategy = _strategyResolver.Resolve(url.Host);
// create and decorate your strategy implementation
[StrategyFor("news.ycombinator.com")]
public class HackerNewsStrategy : IMyStrategyInterface
The resolver scans the calling assembly for implementations of TStrategy. When any are found they are stashed away in a dictionary.
Later we can retrieve them by the key. If nothing is found then we will return the default(TStrategy), which is probably going to be null.
Whether this is a good implementation or idea or not, I am finding it quite nice to be able to just add a new class implementing my IStrategy, decorate it and... that's it. Done.
The resolver class:
public class StrategyResolver<TStrategy>
{
private static Dictionary<string, TStrategy> _strategies;
public TStrategy Resolve(string key)
{
var assembly = Assembly.GetCallingAssembly();
return ResolveImpl(key, assembly);
}
public TStrategy Resolve(string key, TStrategy defaultStrategy)
{
var assembly = Assembly.GetCallingAssembly();
return ResolveImpl(key, assembly, defaultStrategy);
}
private static TStrategy ResolveImpl(string key, Assembly callingAssembly, TStrategy defaultStrategy = default(TStrategy))
{
if (_strategies == null) Load(callingAssembly);
var lowerKey = key.ToLowerInvariant();
if (_strategies != null && _strategies.ContainsKey(lowerKey))
{
return _strategies[lowerKey];
}
return defaultStrategy;
}
private static void Load(Assembly assembly)
{
_strategies = new Dictionary<string, TStrategy>();
var strategyType = typeof(TStrategy);
var types = assembly
.GetTypes()
.Where(t => strategyType.IsAssignableFrom(t) && t.IsClass)
.ToList();
foreach (var type in types)
{
var strategyFor = type.GetCustomAttributes(typeof(StrategyForAttribute), false).FirstOrDefault();
if (strategyFor == null) continue;
var strategyForAttribute = strategyFor as StrategyForAttribute;
if (strategyForAttribute == null) continue;
_strategies[strategyForAttribute.Key] = (TStrategy)Activator.CreateInstance(type);
}
}
}
Each strategy implementation is decorated with the [StrategyFor] attribute.
[AttributeUsage(AttributeTargets.Class)]
public class StrategyForAttribute : Attribute
{
public StrategyForAttribute(string key)
{
Key = key;
}
public string Key { get; private set; }
}