På arbejde er det blevet besluttet at benytte Unity som DI framework, da det er "standard" (læs: det er fra Microsoft). En irriterende beslutning synes jeg, da jeg er stor Castle Windsor fan. Men nu vel Unity fungerer udemærket - bortset fra sin mangle på support for IDisposable.
Et irritationsmoment ved Unity er dog når man skal benytte decorators. Her er det super nemt i Castle Windsor, som har en naturlig forståelse for Decorator mønsteret - hvilket Unity desværre ikke har.
Antag at du har følgende interface "IFileReader" til at læse data fra en fil, og en klasse "CachedFileReaderDecorator", som er en decorator der implementerer caching til dette interface:
public interface IFileReader {
string ReadFileAsString(string fileName);
}
public class CachedFileReaderDecorator {
private readonly IFileReader baseFileReader;
private readonly Dictionary<string,string> cache = new Dictionary<string,string>();
public CachedFileReaderDecorator(IFileReader fileReader) {
baseFileReader = fileReader;
}
public string ReadFileAsString(string fileName) {
// Load file into cache - if not there already
if (!cache.ContainsKey(fileName)) {
cache[fileName] = baseFileReader.ReadFileAsString(fileName);
}
return cache[fileName];
}
}
I Castle Windsor vil dette resultere i følgende kode, for at registere og herfeter få en instans (resolve) decoratoren:
public void CastleResolveExample() {
var container = new WindsorContainer();
container.AddComponent<IFileReader, FileReader>();
container.AddComponent<IFileReader, CachedFileReaderDecorator>();
var cachedFileReader = container.Resolve<IFileReader>();
I eksemplet udnyttes at Castle Windosr forstår Decorator mønsteret, hvor konventionen er, at man først registrer den inderst klasse og herefter registrer decoratorne i den ønskede rækkefølge, således at den yderste decorator registeres til sidst. På denne måde bestemmer registreringsrækkefølgen hvordan decoratorne skal benyttes ift basisklassen og evt. øvrige decoratorer.
Denne konvention findes desværre ikke i Unity, som ikke har en særlig forståelse af decorator mønsteret, her er det nødvendigt at lave en registrering således:
public void UnityResolveExample() {
var container = new UnityContainer();
container.RegisterType<IFileReader, FileReader>("FileReader");
container.RegisterType<IFileReader, CachedFileReaderDecorator>(
new InjectionConstructor(new ResolvedParameter<IFileReader>("FileReader"))
);
var cachedFileReader = container.Resolve<IFileReader>();
}
Umiddelbart er dette "kun" en smule mere besværligt end i Castle Windsor. Men hvis ens decorator tager flere parameter i constructoren skal disse også registerets via flere ResolvedParamaters i InjectionConstructor. Dette er både besværligt, og kan nemt lede til fejl, da man skal rette i registeringen af CachedFileReaderDecorator, hver gang man ændre i klassens constructor. Dette er virkelig skidt!
Løsningen
Heldigvis er Unity relativ nem at skrive udviddelser (extensions) til - og på følgende blog http://www.beefycode.com/post/Decorator-Unity-Container-Extension.aspx findes en Unity extension, som giver mulighed mulighed for at benytte "Castle Windsors konvension" til registrering af decorators i Unity:
public void UnityResolveExample_WindsorStyle() {
var container = new UnityContainer();
container.AddExtension(new DecoratorContainerExtension()); // Add extension for Castle Windsor style decorators
container.RegisterType<IFileReader, FileReader>();
container.RegisterType<IFileReader, CachedFileReaderDecorator>();
var cachedFileReader = container.Resolve<IFileReader>();
}
Sådan skal det være!