-
Notifications
You must be signed in to change notification settings - Fork 1
Inversion of Control
According to Wikipedia "Inversion of Control, or IoC, is an abstract principle describing an aspect of some software architecture designs in which the flow of control of a system is inverted in comparison to procedural programming."
Inversion of control serves the following design purposes:
- To decouple the execution of a task from implementation.
- To focus a module on the task it is designed for.
- To free modules from assumptions about how other systems do what they do and instead rely on contracts.
- To prevent side effects when replacing a module.
Let's consider the example, when a class, illustrated as HelloWorld below, uses another class Console:
namespace ClassLibrary
{
using System;
class Console
{
public void WriteLine(string line)
{
System.Console.WriteLine(line);
}
}
class HelloWorld
{
ClassLibrary.Console _console;
public HelloWorld()
{
_console = new ClassLibrary.Console();
}
public void SayHello()
{
_console.WriteLine("Hello");
}
}
}
The consumer HelloWorld needs the consumed class Console to write text "Hello" to the console. That’s all good and natural, but does HelloWorld really need to know that it uses Console?
Isn’t it enough that HelloWorld knows that it uses something that has the behavior, the methods, properties etc, of Console without knowing who actually implements the behavior?
By extracting an abstract definition of the behavior used by HelloWorld in Console, represented as IConsole below, and letting the consumer HelloWorld use an instance of that instead of Console it can continue to do what it does without having to know the specifics about Console.
In the next example Console implements IConsole and HelloWorld uses an instance of IConsole. While it’s quite possible that HelloWorld still uses Console what’s interesting is that HelloWorld doesn’t know that. It just knows that it uses something that implements IConsole.
That could be Console, but it could also be, for example, RemoteConsole, TraceConsole or something given that they implement IConsole. Of course this discussion is rather abstract at the moment, but we’ll get to how to implement this in a second using either Dependency Injection or Service Locator.
For now, let’s just assume that we can and that we can change what implementation of IConsole Console uses at runtime. What benefits can we get from that?
Well, first of all HelloWorld is not dependent on Y anymore and it’s therefore less likely that we’ll have to make changes to X when we make changes in Y as we’re less likely to write code in X that relies on implementation details in Y.
Furthermore HelloWorld is now much more flexible as we can change which implementation of I it uses without changing anything in HelloWorld.
Perhaps Console is a component for writing a text to the console and we want HelloWorld, who used to write a text, to start writing a text to a trace console instead using TraceConsole who also implements IConsole. Since HelloWorld no longer relies specifically on TraceConsole but on something that implements IConsole we don’t have to modify anything in HelloWorld.
Another benefit of inversion of control is also that we can isolate our code when creating unit tests.
Suppose, as before, that HelloWorld used Console to write texts which Console. When creating unit test, which should only test a single unit of code, we want to be able to test the logic in HelloWorld without having to care about the component that write a text to the console.
Having used inversion of control HelloWorld doesn’t rely on Console anymore but on an implementation of IConsole that just happens to be Console.
That means that we, in the setup phase of our tests, can change it so that HelloWorld uses a different implementation of IConsole, such as a mock object which doesn’t send any messages at all, and which also allows us to check that HelloWorld has used it in a correct way.
Enough with the HelloWorld and the Console and IConsole! Let’s look at the following example:
[Contract(typeof(Program))]
public class Program
{
public static void Main()
{
using (
var container = new Container()
.Configure()
.DependsOn<JsonConfiguration>(jsonConfigStr)
.Register().AsAutowiring(typeof(Program))
.ToSelf())
{
container.Resolve().Instance<Program>();
}
}
public Program(IHelloWorld helloWorld)
{
helloWorld.SayHello();
}
}
The Json configuration json srting:
[
{ "reference": "ClassLibrary" },
{ "using": "ClassLibrary" },
{ "register": [ { "contract": [ "IConsole" ] } ], "autowiring": "Console" },
{ "register": [ { "contract": [ "IHelloWorld" ] } ], "autowiring": "HelloWorld" }
]
namespace ClassLibrary
{
internal interface IConsole
{
void WriteLine(string line);
}
public interface IHelloWorld
{
void SayHello();
}
}
namespace ClassLibrary
{
using System;
// Has no any dependencies
internal class Console : IConsole
{
public void WriteLine(string line)
{
System.Console.WriteLine(line);
}
}
// Has the only one dependency implementing interface "IConsole"
internal class HelloWorld : IHelloWorld
{
private readonly IConsole _console;
public HelloWorld(IConsole console)
{
_console = console;
}
public void SayHello()
{
_console.WriteLine("Hello");
}
}
}