Interface Based Programming and IOC Containers
Inheritance is surely a good answer but who knows the questions? -Michel Gauthier
When it comes to writing good software, this quotes fits very well. It can be a bit tricky to understand when to use the right things, understanding and knowing the right way to use them. Many developers thing the key to get software working is:
MyClass m = new MyClass();
m.DoSomething();
and job done!
But there is more to it. At my work place, I was taking to this person ( on some other team ) who comes to me asking "Why are we using a dependency injector?". I've seen him around and I know that he is writing code for a few months and after having developed a few modules, he should be aware of why he is writing code that way. I asked him "Do you have an understanding of an Interface and why should we use them?". Yes!, he knows what is written in books but the practical usability is not there. He is unable to give an example of why and where he would want to declare an Interface?
Such things are very common and The real problem is that many developers doesn't really understand the purpose of Interface and why software needs them.
So let's take a look at why would you like to use Interfaces while writing code.
Simplest of the Example:
Scenario : Have a look at the following code examples.
Business.cs :
using System;
namespace UsingInterfaces
{
public class Business
{
public void DoSomething(string text)
{
string outPut = null;
for (int i = 0; i < 5; i++)
{
outPut += text + " ";
}
Console.WriteLine("Output :" + outPut);
}
}
}
Program.cs :
using System;
namespace UsingInterfaces
{
class Program
{
static void Main(string[] args)
{
Business business = new Business();
business.DoSomething("Hello");
Console.WriteLine("Press enter key to exit...");
Console.ReadLine();
}
}
}
This will print out input string "Hello" five times.
Let's have a look at the Business class. It concatenates the string five time in a loop. Now let's assume that we don't like this code and want to use StringBuilder class to implement our logic. So we created a new class BusinessNew with the following code. Note that the method name remains the same and only it's implementation is changed.
BusinessNew.cs :
using System;
using System.Text;
namespace UsingInterfaces
{
public class BusinessNew
{
public void DoSomething(string text)
{
StringBuilder outPut = new StringBuilder();
for (int i = 0; i < 5; i++)
{
outPut.Append(text);
outPut.Append(" ");
}
Console.WriteLine("Output :" + outPut.ToString());
}
}
}
Now we want to change our code to use this new logic. To do this we need to update the Program.cs code to use BusinessNew.
Business business = new Business();
should be updated to
BusinessNew business = new BusinessNew();
and nothing is broken in the application.
This change is to applied to every place in the application.
Here is a situation where Interface can help you. Note that both the Business and BusinessNew classes defines the same method but the difference is implementation of the methods.
From general definition of an Interface, it is a contract that any type which implements it should follow by implementing all methods defined within the contract i.e. Interface.
So in our case, lets define an interface IBusiness with the only one method DoSomething.
IBusiness.cs:
namespace UsingInterfaces
{
public interface IBusiness
{
void DoSomething(string text);
}
}
Let's update our Business and BusinessNew classes to implement this interface.
public class Business : IBusiness
and
public class BusinessNew : IBusiness
Since both these classes defines the required method already nothing is broken in our application.
Now let's update our Program.cs code to use:
IBusiness business = new BusinessNew();
Writing code like this now has following benefits:
Since you have Concrete instance (BusinessNew) defined only once, if next day you want to switch back to Business class, you need to update only once place in the code. Last update from Business to BusinessNew, we needed to updated code at two places changing the instance to be used. All it needs a concrete instance that must implement IBusiness interface.
Next thing is that if you change the Concrete instance and add another method to it, then it is not visible to the outside code using your concrete type through the eyes of interface, unless the new method is added to the interface (Think about this!). It's like, you can call only the methods that exists with the interface.
Interface also plays a very important role when it comes to interacting with COM components.
Now you should have a better understanding of "Why Interfaces?".
Let's move our discussion to IOC containers. IOC is "inversion of control" and it's main purpose is to inject dependency in your code without you to create an instance of a Type.
IBusiness business = new BusinessNew();
Although this code is little better than our previous version of the code, but if we could eliminate the code on the right side and make it more simple by remove this dependency on BusinessNew. That's the case when you would want to use IOC container.
An IOC is a container just like a box, that is instructed to make bindings of abstraction (interfaces) with concrete types. Such a container will bind all the types at the application start and then your code will request this container to supply a concrete instance for a abstract type (here we are talking about interfaces as abstractions and not abstract classes) supplied by you.
Let's try Ninject for this purpose. Download it or use nugget to add a reference to Ninject to the project. Here I've defined DependencyModule type that will define all the bindings for the abstract type with corresponding concrete type.
DependencyModule.cs:
using Ninject.Modules;
namespace UsingInterfaces
{
public class DependencyModule : NinjectModule
{
public override void Load()
{
Bind<IBusiness>().To<BusinessNew>();
}
}
}
Here we say that whenever the code asks for IBusiness we provide a concrete BusinessNew type.
Now that we have set the bindings, we need to define another type to expose them to our code.
DependencyInjector.cs:
using Ninject;
namespace UsingInterfaces
{
public static class DependencyInjector
{
public static IKernel Kernel = new StandardKernel(new DependencyModule());
}
}
Here we pass our DependencyModule to the StandardKernel type defined with Ninject. Now we are ready to update our Program.cs.
Updated Program.cs:
using Ninject;
using System;
namespace UsingInterfaces
{
class Program
{
static void Main(string[] args)
{
IKernel kernel = DependencyInjector.Kernel;
IBusiness business = kernel.Get<IBusiness>();
business.DoSomething("Hello");
Console.WriteLine("Press enter key to exit...");
Console.ReadLine();
}
}
}
All things are working the same way as before, but did you notice the difference in the code and it's maintainability.
Now in our code you will never have any need to instantiate BusinessNew. You should call IOC to return you the instance of BusinessNew.
Let's say next day if want to move back to our Business class implementation, we need to update only one place in our entire application i.e. the IOC binding.
Change DependencyModule:
Bind<IBusiness>().To<BusinessNew>();
to
Bind<IBusiness>().To<Business>();
and you have updated your entire application to use this new Type saving you a lot of time.
That is why there is so much focus on IOC containers in software. Using IOC containers makes the application easier to maintain and update. Now you should have a better understanding of Why you need Interfaces and then IOC containers.
Download the code here.
"Happy Coding :)"
No new comments are allowed on this post.
Comments
No comments yet. Be the first!