AUTOMATIC INVERSION OF CONTROL TOOLS! ARE THERE ANY MAJOR BENEFITS?

AUTOMATIC INVERSION

Automatic Inversion/Dependency Injection as a design pattern has been around for a while now…and it’s one I quite like. At a very simple level it makes changing components (dependencies) easier. I have also found that applying the design pattern to our code makes it’s easier to test and improves the overall maintainability.

There are lots of articles available on the subject such as:

http://en.wikipedia.org/wiki/Dependency_injection

http://msdn.microsoft.com/en-us/magazine/cc163739.aspx

http://martinfowler.com/articles/injection.html

Up until now anytime I applied the pattern I wrote my own code to inject the dependencies i.e. manual dependency injection.  However there are now a variety of tools and packages that will perform the dependency injection for us i.e. automatic injection. These tools are often called inversion of control containers.

There seems to be more of these tools available every time I look. Some of the more popular ones include: Ninject, StructureMap, Unity and Autofac. One of my colleagues has written a piece with some comparisons of them: Inversion of Control Review.  

I figured it’s about time I caught up with these, so I decided to put together a quick sample application to try them out.

I am going to take a small part of a c# timesheet application we have and alter it to support both manual and automatic dependency injection. The application keeps track of different jobs done each week and the amount of hours taken up by them.

For simplicity sake I’ve just made this sample a windows console application. First we will create 2 interfaces:

    public interface IJob
    {
        string Customer { get; set; }
        string Description { get; set; }
        int Hours { get; set; }
    }

    public interface ITimeSheet
    {
        int Year { get; set; }
        int Week { get; set; }
        List<IJob> Jobs { get; set; }

        string GetTotalHours();
        void AddJob(IJob j);
    }

 

And now an implementation for each of them

public class StandardJob : IJob
    {
        public string Customer { get; set; }
        public string Description { get; set; }
        public int Hours { get; set; }

        public StandardJob()
        {
            this.Customer = "";
            this.Description = "";
            this.Hours = 0;
        }

        public StandardJob(string customer, string description, int hours)
        {
            this.Customer = customer;
            this.Description = description;
            this.Hours = hours;
        }
    }

    public class StandardTimesheet : ITimeSheet
    {
        public int Year { get; set; }
        public int Week { get; set; }
        public List<IJob> Jobs { get; set; }
        public string GetTotalHours()
        {
            return Jobs.Sum(j => j.Hours).ToString();
        }

        public void AddJob(IJob j)
        {
            Jobs.Add(j);
        }

        public StandardTimesheet(int year, int week, List<IJob> jobs)
        {
            this.Year = year;
            this.Week = week;
            this.Jobs = jobs;
        }

        public StandardTimesheet(int year, int week)
        {
            this.Year = year;
            this.Week = week;
            this.Jobs = new List<IJob>();
        }
    }

For convenience I’ve also create a small static method to start everything off.

        public static void Go(ITimeSheet t, IEnumerable<IJob> jobs)
        {
            foreach (var j in jobs)
            {
                t.AddJob(j);
            }
            Console.WriteLine(t.GetTotalHours());
        }

For the automatic injection part, I’ve decided to go with Ninject, mostly based on the name. It seems to be equally as good as any of the others. You can install it via the nugget command: Install-Package Ninject.

So in our program.cs file we can have:

static void Main(string[ ] args)
        {
            NotUsingContainer();
        }

        static void NotUsingContainer()
        {
            var t = new StandardTimesheet(2014,2);
            var j1 = new StandardJob();
            j1.Customer = "bob";
            j1.Description = "Test 1";
            j1.Hours = 5;

            var j2 = new StandardJob();
            j2.Customer = "tom";
            j2.Description = "Test 2";
            j2.Hours = 9;
           IJob [ ] js = { j1, j2 };
          Go.GoGo(t, js);
          Console.ReadLine();
        }

As you can see we are injecting the StandardTimesheet and StandardJob implementations into our program. If we wanted to use a new implementation of ITimesheet we would only need to change the line

var t = new StandardTimesheet(2014,2);

to use our new class e.g. we could have

var t = new ExtendedTimesheet(2014,2);

assuming that ExtendedTimesheet also implements ITimesheet.

Now let’s try that with our injection tool. We need to first tell our tool how we want to resolve the dependencies. Some tools use an xml or other type of configuration file and some do it in code. Ninject takes the code approach.

So we will have a class like

    class TestModule : Ninject.Modules.NinjectModule
        {
            public override void Load()
            {
                Bind<ITimeSheet>().To<StandardTimesheet>();
                Bind<IJob>().To<StandardJob>();
            }
        }

This tells our tool to us the StandardTimesheet for ITmesheet and the StandardJob implementation for IJob. This is a very simple example – the tool is much more flexible than that.

So now we can add a function like the following to our program.cs

 

static void UsingContainer()
        {
            IKernel kernel = new StandardKernel(new TestModule());
            var t = kernel.Get<ITimeSheet>(new ConstructorArgument("year", 2014),
                        new ConstructorArgument("week", 2));
            var j1 = kernel.Get<IJob>();
            j1.Customer = "bob";
            j1.Description = "Test 1";
            j1.Hours = 5;

            var j2 = kernel.Get<IJob>();
            j2.Customer = "tom";
            j2.Description = "Test 2";
            j2.Hours = 9;

            IJob[ ] js = { j1, j2 };
            Go.GoGo(t, js);
            Console.ReadLine( );       
        }

The first couple of lines in the function are loading up the module we previously created with our injection configuration and then uses the Get method of Ninject to load our object. As you can see there is no mention of the implementation we are using here.

In this simple example there doesn’t seem to be much point to using the automatic tool. We’ve still conformed to our pattern. Both versions are still testable and maintainable.

If we had a more complicated example with multiple dependencies I can see how there would be less changes to the code needed when using a tool. However the program operation itself still changes when we inject a new dependency regardless of what approach we take. The same testing and build/rollout activities would still need to take place.

I will have to apply Ninject or one of the other IoC tools to an actual project before I make up my mind, but at the moment I am struggling to see any major benefits….don’t forget to check out our blog reviewing Inversion of Control : Inversion of Control Review

 

At Dataworks we enable the perfect hybrid of configurable off the shelf toolsets and custom software development to deliver innovative solutions to match your specific business process requirements. This ensures we are the best at what we do.

If you would like to discuss how we can use our experience and expertise to deliver real benefits toyour business please contact us today on 051 878555051 878555 or email info@dataworks.ie

Share on facebook
Share on google
Share on twitter
Share on linkedin
Share on whatsapp