1. |
Inversion of Control is a pattern in programming which allows classes to program to interfaces
and not to concrete instances of classes. This allows the implementation of service classes to
to remain hidden from the calling class. The advantages of this is that changing the type of the concrete service class does not require you to change the type of the object in your calling class. This helps when switching between service classes with a similar intention - you may have an Filetype object with a Save class which saves to file, you may have a DBtype object with a Save method which stores in a database, and a Webtype class which has a Send method which sends the data to a web service for storage. As long as each of these classes are inherited from a common base interface which defines the Save method, then the app only has to invoke a new instance of the Interface and call the Save method, which particular concrete class is actually called is up to the Container class which is separately defined. This is called Inversion of Control, because the caller object does not control which service objects it invokes. The method by which the Interface is linked to a concrete class in another place is what an IoC container framework gives you. It allows you to separately link interfaces to concrete classes at runtime (or in a config) so that when your app calls for an object of a specific interface type, the framework supplies a concrete class of the type defined in the mapping. This allows you to do two things more easily - for testing (or TDD) - it allows you to quickly and separately switch in and out testing stubs and harnesses so that you can test each part in true isolation. Secondly, it gives you a way to create configurable objects - which makes the Factory pattern very easy to implement. StructureMap is a popular framework for Inversion of Control, using the Dependency Injection method. | ||||||||||||
2. | Download the StructureMap dll from its website. Only the dll is actually required for now, so unzip the folder and save all of
the files in a named folder somewhere on your hard drive. Create a new Console app in Visual Studio and add a Reference to the StructureMap.dll (by Add Reference and then browsing to the saved folder). | ||||||||||||
3. | Instantiate an IContainer (which lies within StructureMap, so
remember the using line!) and call off to a new local method for now called ConfigureDependencies -
this is where we will wire up interfaces to concrete classes, but we'll leave the detail of that for now.
Create a new IAppEngine object and then call the GetInstanceusing System; using StructureMap; class Program { private static void Main(string[] args) { IContainer IOCcontainer = ConfigureDependencies(); IAppEngine appEngine = IOCcontainer.GetInstance<IAppEngine>(); appEngine.Run(); } } | ||||||||||||
4. | Obviously we need a definition for the IAppEngine interface, defining its single Run method.
public interface IAppEngine { void Run(); } | ||||||||||||
5. | Now we need a concrete definition of an IAppEngine object, which
we can wire up to the IAppEngine interface in the IoC container in a minute. We leave it with an empty
constructor for now, and the Run method simply prints hello to the console.
public class AppEngine : IAppEngine { public AppEngine() { } public void Run() { Console.WriteLine("Hello World"); } } | ||||||||||||
6. | Now the clever bit, we need to implement the configuration section which
wires up the Interface to the concrete class, so that when the Main program calls app.Run, it will run it on
the concrete class defined in the mapping:
private static IContainer ConfigureDependencies() { return new Container(x => { x.For<IAppEngine>().Use<AppEngine>(); }); }This function creates and returns a new Container (which lives in StructureMap) and does one linkup, it states: FOR interfaces of type IAppEngine USE concrete class AppEngine Then at test time, you can switch a different concrete class in very easily for testing. private static IContainer ConfigureDependencies() { return new Container(x => { //x.For<IAppEngine>().Use<AppEngine>(); x.For<IAppEngine>().Use<TestEngine>(); }); } | ||||||||||||
7. | One class is a bit of a noddy example. Let's create new classes for providing the
hello message and new classes for printing out to the console:
| ||||||||||||
8. | Now we need two new interfaces, one to cover the Greeter classes, and one to cover the Output classes:
public interface IGreeter { string GetGreeting(); } public interface IOutputDisplay { void Show(string message); } | ||||||||||||
9. | Now we can change our concrete AppEngine class to use the new object types:
public class AppEngine : IAppEngine { private readonly IGreeter greeter; private readonly IOutputDisplay outputDisplay; public AppEngine(IGreeter greeter, IOutputDisplay outputDisplay) { this.greeter = greeter; this.outputDisplay = outputDisplay; } public void Run() { outputDisplay.Show(greeter.GetGreeting()); } }This has changed quite considerably. The constructor now takes two objects of the types for Greeter and OutputDisplay, and keeps local references to them. These objects are then used both to get the message to display and to print it to the console. At this stage we don't know which of the specific concrete objects are going to be used, because we haven't wired them up in the configuration section yet. | ||||||||||||
10. | We can add the new specific mappings to the concrete objects to the ConfigureDependencies method, say we decide we want to use the EnglishGreeter and to have it wait for a keypress:
private static IContainer ConfigureDependencies() { return new Container(x => { x.For<IAppEngine>().Use<AppEngine>(); x.For<IGreeter>().Use<EnglishGreeter>(); x.For<IOutputDisplay>().Use<ConsoleOutputDisplayPlusWait>(); }); }Obviously we can have any of the 3 concrete classes for IGreeter, and any of the 3 concrete classes for IOutputDisplay | ||||||||||||
11. | Obviously we need to implement the various classes:
Moreover, by passing parameters into ConfigureDependencies method, you can programmatically configure the AppEngine so you would get a different behaviour depending on your initial configuration. | ||||||||||||
12. | Instead of having a programmatic configuration method called ConfigureDependencies,
StructureMap allows you to configure using an XML file (typically called StructureMap.config, but it can live in any
file, including web.config or App.config):
<?xml version="1.0" encoding="utf-8" ?> <StructureMap> <Assembly Name="MyConsoleApp" /> <PluginFamily Type="IAppEngine" Assembly="MyConsoleApp" DefaultKey="MainApp"> <Plugin Type="AppEngine" Assembly="MyConsoleApp" ConcreteKey="MainApp" /> </PluginFamily> <PluginFamily Type="IGreeter" Assembly="MyConsoleApp" DefaultKey="EnglishHello"> <Plugin Type="EnglishGreeter" Assembly="MyConsoleApp" ConcreteKey="EnglishHello" /> <Plugin Type="FrenchGreeter" Assembly="MyConsoleApp" ConcreteKey="FrenchHello" /> <Plugin Type="GermanGreeter" Assembly="MyConsoleApp" ConcreteKey="GermanHello" /> </PluginFamily> <PluginFamily Type="IOutputDisplay" Assembly="MyConsoleApp" DefaultKey="ShowAndWait"> <Plugin Type="ConsoleOutputDisplay" Assembly="MyConsoleApp" ConcreteKey="ShowAndVanish" /> <Plugin Type="ConsoleOutputDisplayPlusWait" Assembly="MyConsoleApp" ConcreteKey="ShowAndWait" /> <Plugin Type="ConsoleOutputAborted" Assembly="MyConsoleApp" ConcreteKey="ShowNothing" /> </PluginFamily> </StructureMap>Each PluginFamily corresponds to the IoC mapping for that interface, each possible switchable concrete implementation is listed as a Plugin with a unique ConcreteKey. The PluginFamily defines one of them as the DefaultKey. | ||||||||||||
13. | Now instead of a ConfigureDependencies method, we change the init sequence to:
private static void Main(string[] args) { ObjectFactory.Initialize(x => { x.UseDefaultStructureMapConfigFile = true; }); IAppEngine appEngine = ObjectFactory.GetInstance<IAppEngine>(); appEngine.Run(); }ObjectFactory is initialized to use the default configuration file (which is StructureMap.config), then the default Instance of the IAppEngine interface is requested. This uses the DefaultKey setting in the XML to return the default one. If a specific one is required, use: IAppEngine appEngine = ObjectFactory.GetNamedInstance<IAppEngine>("MainApp"); | ||||||||||||
And that is the basics of using the StructureMap IoC container. |