UPDATE: This post is pretty old. Please don’t use XML to configure Windsor, unless you’re aware of the nifty fluent registration API, but you absolutely need the runtime-flexibility that XML can offer.
A great pattern in software architecture is dependency injection (DI). It is a classical pattern apparently, but it seems to have become very popular again in TDD circles because of its obvious positive impact on testability. Moreover, I believe it is a healthy architectural exercise to structure your code to support DI, because it enforces separation of concerns.
Dependency injection can be explained like this: if X requires a Y to do its work, X does not create the Y by itself, it is given a Y to use. Thus, DI is an example of inversion of control (IoC), which is a fancy term for whenever you pass something for someone to call functions on.
In the following post, I will give a short example on how to practice DI using the Windsor IoC container.
Suppose we need to print the contents of a file to the console. We create a
FileReader to read the file line by line, and a
LineWriter to print each line to the console. When we create the
FileReader, we supply an instance of
LineWriter for the reader to use. Like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
namespace DI.SimpleExample { public class LineWriter { public void WriteLine(string line) { Console.Out.WriteLine(line); } } public class FileReader { LineWriter lineWriter; public FileReader(LineWriter lineWriter) { this.lineWriter = lineWriter; } public void ReadFile(string fileName) { using (StreamReader reader = new StreamReader(fileName)) { string line; while ((line = reader.ReadLine()) != null) { lineWriter.WriteLine(line); } } } } } |
Now, to make things work, we wire them up like this:
1 2 3 |
FileReader reader = new FileReader(new LineWriter()); reader.ReadFile(testFileName); |
-and that’s basically it! We took two classes with a dependency, and we injected an instance of LineWriter into the FileReader. But even though we control which instance goes into the FileReader, we still have not decoupled the two classes completely. That’s because the FileReader expects one particular implementation of LineWriter. To decouple them fully, we extract the LineWriter’s functionality into an interface, and make the FileReader be dependent of an ILineWriter instead. Like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
namespace DI.WindsorExample { public interface ILineWriter { void WriteLine(string line); } public interface IFileReader { void ReadFile(string fileName); } public class LineWriter : ILineWriter { public void WriteLine(string line) { Console.Out.WriteLine(line); } } public class FileReader : IFileReader { ILineWriter lineWriter; public FileReader(ILineWriter lineWriter) { this.lineWriter = lineWriter; } public void ReadFile(string fileName) { using (StreamReader reader = new StreamReader(fileName)) { string line; while ((line = reader.ReadLine()) != null) { lineWriter.WriteLine(line); } } } } } |
Now, to actually use the classes, we could of course just instantiate a FileReader, supplying a LineWriter for it to use. But another way is to let an IoC container take care of wiring things up. This is where Windsor comes in – check this out:
1 2 3 4 5 6 7 |
WindsorContainer container = new WindsorContainer(); container.AddComponent("linewriter", typeof(ILineWriter), typeof(LineWriter)); container.AddComponent("filereader", typeof(IFileReader), typeof(FileReader)); IFileReader reader = container.Resolve<IFileReader>(); reader.ReadFile(testFileName); |
– and this will actually work! What happens, is that we create a container, tell it which implementations (“types”) to supply when asked for the corresponding interfaces (“services”), and then we let Windsor do the rest. When prompted to resolve a service (an implementation of IFileReader), Windsor will attempt to create an instance, resolving any needed services along the way, thus, automatically wiring up the two instances. How cool is that?!
The only thing left is to separate the configuration of the Windsor container from your application code. The construct above is a little awkward, and you would probably have put the container initialization somewhere else. A nice – and somewhat more declarative – way of configuring the container is by supplying the configuration in an XML file. Check the following example:
1 2 3 4 5 |
WindsorContainer container = new WindsorContainer(new XmlInterpreter()); IFileReader reader = container.Resolve<IFileReader>(); reader.ReadFile(testFileName); |
– and the configuration is put the App.config file. It looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" /> </configSections> <castle> <components> <component id="di.windsorExample.lineWriter" service="DI.WindsorExample.ILineWriter, DI" type="DI.WindsorExample.LineWriter, DI" /> <component id="di.windsorExample.fileReader" service="DI.WindsorExample.IFileReader, DI" type="DI.WindsorExample.FileReader, DI" /> </components> </castle> </configuration> |
God lille tutorial. Jeg så jo dit foredrag hos Trifork idag – det gjorde du sku’ godt. Jeg har aldrig brugt tid på at sætte mig ind i windsor-containeren, så det var fedt at få en lille intro til denne. Overraskende smart at den kan resolve paramatiserede constructors uden ekstra arbejde med opsætningen.