Categories

Archives

The latest TLA (Three Letter Acronyms) du jour seem to be SoC, DI, and IoC. What do these stand for and why should you care about them?

Consider the following (very) simple console application:

Code Snippet

using System;

namespace TightlyBoundTestApp

{

publicclassProgram

{

staticvoid Main(string[] args)

{

// Load up a customer

Customer c = newCustomer();

// Logging should have written a message

c.Log();

Console.ReadKey();

}

}

publicclassCustomer

{

privateLogger logger;

public Customer()

{

logger = newLogger();

}

publicvoid Log()

{

logger.LogMessage("This should be logged");

}

}

publicclassLogger

{

publicvoid LogMessage(string message)

{

Console.WriteLine("Log: " + message);

}

}

}

In this sample application, we have a Customer class which (for example purposes) only has a single method: Log, which allows it to log a message. We might use this to keep track of the transactions done by a customer so we have an audit trail for example. The logging happens by calling the LogMessage method on an instance of the Logger class which the Customer class created in its constructor.

In our entry point to the program, we create a new instance of our Customer class, and tell it to log a message. If we run this, a log message will be printed to the console, then the program will exit once we press a key.

So, it all works okay, but there a few problems which may cause issues for us down the track. We have made our Customer class depend on our Logger class. What happens when we want to create unit tests for our Customer class? We would also have to consider our Logger class as they are tightly coupled.

This violates the first of our TLAs: Separation of Concerns (SoC). "Separation of Concerns is the process of separating a computer program into distinct features that overlap in functionality as little as possible. A concern is any piece of interest or focus in a program.” – Wikipedia

How do we go about achieving this separation then? Instead of having our Customer class depend on a concrete implementation of a Logger class, we could change it to interact with an interface instead. The principle behind this is known as Dependency Inversion – "High-level modules should not depend on low-level modules. Both should depend on abstractions." – Wikipedia.

By changing the Customer class to interact with an ILogger interface, we could provide one implementation in our Application, and a completely different one in our tests. This design technique is known as Programming to Contract.

So, let’s start refactoring our sample program by creating an interface for the Logger:

Code Snippet

publicinterfaceILogger

{

void LogMessage(string message);

}

And then we can implement that interface in our Logger class:

Code Snippet

publicclassLogger : ILogger

{

publicvoid LogMessage(string message)

{

Console.WriteLine("Log: " + message);

}

}

And in our test project we could implement it differently, maybe keep a reference to the last message logged so we can check it in our tests for example:

Code Snippet

publicclassTestLogger : ILogger

{

publicstring LastMessage { get; set; }

publicvoid LogMessage(string message)

{

LastMessage = message;

}

}

Great, so we are now programming to contract and have an interface for our logger. But how does our Customer class know which concrete implementation of the ILogger to use? If we keep the following code:

Code Snippet

public Customer()

{

this.logger = newLogger();

}

We’re no better off that where we were originally, as the Customer class is still using a concrete implementation of the Logger class. What we need to do instead is to tell which implementation of the ILogger to use. There’s a few different ways to do this. The simplest way to do this is to provide the implementation in the constructor, by changing our Customer class to the following:

Code Snippet

publicclassCustomer

{

privateILogger logger;

public Customer(ILogger logger)

{

this.logger = logger;

}

publicvoid Log()

{

logger.LogMessage("This should be logged");

}

}

Now when we instanciate our Customer in the main program, we pass in the implementation of the ILogger we wish to use at that point, like so:

Code Snippet

// Create a new customer

Customer c = newCustomer(newLogger());

And in our tests, we could simply pass in an alternative implementation:

Code Snippet

// Create a new customer with a test logger

Customer c = newCustomer(newTestLogger());

This is the simplest form of the next of our TLAs: Dependency Injection (DI) – "a technique for supplying an external dependency (i.e. a reference) to a software component – that is, indicating to a part of a program which other parts it can use" – Wikipedia.

You could also use a Factory / Service Locator pattern, but we’re going to skip past that and jump straight into the last of our TLAs: Inversion of Control (IoC), specifically IoC Containers. Containers provide a centralised place to manage dependencies. Think of a container as a dictionary which maps interfaces to implemented types. In other words, it resolves dependencies.

The rest of this post will focus on Ninject, as I feel it is the simplest to illustrate the concepts of DI without getting too bogged down. A subsequent post will revist the example using Unity instead to contrast the differences. The important thing is to remember the concepts behind these different tools are the same, so don’t get too hung up on which is ‘best’.

Ninject

‘Bind’ is used to map the interfaces to the implemented types, i.e. if I ask for an <IBurger>, give me a <BigMac>. The binding is usually set up in a module which extends the NinjectModule class. Binding uses generics to provide a fluent interface: Bind<YourInterface>().To<YourConcreteImplementation>(); In our example program it looks like this:

Code Snippet

publicclassStandardModule : NinjectModule

{

publicoverridevoid Load()

{

Bind<ILogger>().To<Logger>();

}

}

‘Get’ is used to actually retrieve your concrete implementations with all their dependencies ‘injected’ into them. Get is a method on the Kernel. The ‘Kernel’ is your container, and is usually an instance of the StandardKernel which is instanciated with the Module that you setup your bindings in as above.

Code Snippet

IKernel kernel = newStandardKernel( newStandardModule() );

// Get a new customer from our container

Customer c = kernel.Get<Customer>();

So, we set up our Kernel with a reference to our module which is binding any request for an ILogger to our Logger class. We then ‘Get’ our Customer class from the Kernel. But hang on, our Customer class isn’t implementing an interface, why do we need to get it using the Kernel? Couldn’t we just instanciate it as usual?

This threw me a little when I first started using Ninject until I realised that unless you get an object via the Kernel, any dependencies that object has will NOT be injected.

So, that’s why we use kernel.Get to retrieve our Customer. By using the kernel, it ensures that the dependency the Customer has on ILogger is injected using the binding we setup in our StandardModule.

// No longer need to instanciate a logger in the constructor as it is injected when required.

}

publicvoid Log()

{

CustomerLogger.LogMessage("This should also be logged");

}

}

By marking the CustomerLogger property with the [Inject] attribute, whenever it is required (in the Log method for instance), Ninject will resolve the dependency, in this case mapping our ILogger to our Logger class as defined in the StandardModule.

In our tests then, we could create a different set of mappings by binding our ILogger to a different implementation, like our TestLogger. Dependency Injection and Mocking go together very well, and there is an extension class for Ninject called Ninject.Moq which allows you to bind directly to a mock, e.g:

Code Snippet

Bind<ILogger>().ToMock<ILogger>();

Hopefully that has helped you to see the power of Dependency Injection. Take a look at the attached solution to see the original tightly coupled application, the decoupled application using constructor injection, and then the same application using Ninject, along with a simple unit test using Ninject.