Tuesday, 21 April 2009

Tech Day #3 – Inversion of Control (IoC) Containers

For my third tech day I had a bit of an internal fight over whether or not I should get started with WPF, or take a look at IoC containers. As you can tell from the title, I went with IoC containers since I thought the knowledge would be more useful to me at this point.

What is an Inversion of Control (IoC) Container?

The first obvious question. Before we answer this through we should talk about some base concepts that are important to IoC. We’ll come back to this in a minute..

What are the Basic Concepts Behind IoC?

The Hollywood Principle

I first heard about this in Head First Design Patterns (I also strongly recommend grabbing the poster). In short this basically says “don’t call us, we’ll call you” – lower-level classes that perform specialised tasks lose their control, instead being called by higher-level classes. These higher-level classes provide the “hooks” for the lower-level classes to “slot in”. Lower-level classes don’t get to call the big-shot higher-level classes.

In the sample code for this post, check out the “HollywoodPrinciple” namespace, there are some simple classes in there that will demonstrate the Hollywood Principle.

Note that the actors have no influence over the director at all, they simply do as they are told. The director maintains control of the application flow by calling the Action and Cut methods, but the actual people responsible for doing the work actually make it happen.

Dependency Injection

Following on from the Hollywood Principle, the next key component is dependency injection. This is basically the declaration of a class that it needs something to do it’s job. Having applied the Hollywood Principle in the previous code, we now have a HollywoodDirector who is in charge of some Actors, but when newing up the director, this is not immediately obvious.. We could look real silly asking the director to start filming a movie if we forget to tell the actors to show up!

This is where dependency injection comes in. We make a clear statement in our API that we need something in order to be able to operate. At this point, I invite you to review the sample code in the “DependencyInjection” namespace.

See we now have a DependentHollywoodDirector? In this class there is only one small, but significant change. We have added a parameter to the constructor, asking for a List<IActor>. We are saying that the director cannot do it’s job without the actors and have completely removed the directors ability to create it’s own pool of actors (read “removed the new List<IActor>”).

Why is this useful? Simply put, it puts the client code of the director in control of how the director operates. Review the demo code and note that we have added another Actor to the mix without altering the director code.

Inversion of Control - Oh, Did You Miss It?

Yeah, it’s pretty subtle, but we have just applied inversion of control to our code. Since the client code (i.e. the “Demo” code) is now actually altering what our director has to work with. It’s important to note that it does not change how the director does his job - he will always call “Action!” and “Cut!” no matter what actors he is working with. We have also made a clear declaration of intent by placing the actor requirement in the constructor.

So, back to our original question..

What is an Inversion of Control (IoC) Container? (Redux)

An IoC container enables us to abstract away the dependency chain introduced by dependency injection.

Think about the Hollywood example we have had so far..

  • We have a director that requires actors.
  • Each actor requires a make up artist, and a stunt double.
  • Each make up artist requires a bunch of cosmetics to work with.
  • Each stunt double needs some great medical insurance.
  • The medical insurance requires financial and health information about the stunt double.

As you can see, if you have a complex object model (which can easily become the case) you could ultimately be looking at crazy code like:

   1: IActor someActor = 
   2:     new Actor(
   3:         new MakeUpArtist(
   4:             new CosmeticsCollection()),
   5:         new StuntDouble(
   6:             new HealthInformation(),
   7:             new FinancialInformation()));

.. and this is a relatively simple model. But do you want to do this for every actor/make up artist/stunt double?

IoC Containers take away this pain by allowing us to dictate to the container what the dependencies are, and how we want to react to them. When we then ask the container to give us an Actor, it will automagically create the required make up artist and stunt double, along with all the required information they need.

Sounds Complicated? Is It?

The funny thing is, the concept of IoC is pretty simple. But as with most things, they can quickly get blown out of proportion to make it also do “cool stuff”. This puts many people (including myself) off of full-blown containers such as Castle Windsor Container, Spring.NET, Autofaq and many more.

That said, I decided I should see what it is like to write a simple IoC container myself. This was intended to be nothing fancy, but simply a little utility to give us what we need.

robcthegeek’s MAC (Major-Awesome Container)

Yes, the above is all lies and propaganda. But let’s continue.

We are only looking for something simple here. Once the info to tell the container what to do has been provided, we simply then want it to new up objects for us, fulfilling their dependency chain.

Review the “MAC” namespace in the sample code.

Note we have a few classes:

  • robcthegeek – which wants to geek out, but needs coffee and a bag of chocolate peanuts.
  • BagOfChocolatePeanuts – they are yummy!
  • CupOfCoffee – all geeks are sustained by caffeine, but this coffee needs sugar!
  • TeaSpoonOfSugar – needs to go in the coffee!

So, whenever we want a robcthegeek to geek out, we need to new up all of this stuff! Let’s seen if the Major-Awesome Container™ can help!

Building the Container

Now, containers can be very fancy and do lots of reflection and the like to improve the syntax required to use them. In the interest of time, I am not going to be doing all that. This is simply to demonstrate the concept of an IoC container, if you want one with bells and whistles, then check out the container projects listed later.

So, what does the MAC need to do?
  • It needs to be able to take the required configuration from us, so it knows which classes to create for us and their related dependencies.
  • It needs to be able to give us the classes!

Right, so lets start at the top, how do we tell it how to create classes for us?

NOTE: The code in the code sample is not supposed to be anywhere near representative of production-quality code!

Setting the MAC Configuration

Basically, we need to say to the MAC “I want a type, and I want it to be created like this”. So I thought I would keep it as simple and have a method like this:

   1: public void Register<T>(Func<T> factory)

Here we simply have a generic method that takes a delegate that must return the type being registered. Can’t be much simpler than that right?

Getting Classes from the Container

Next, we need to get a type from the container, so what could be simpler than:

   1: public T Get<T>()

Bringing it Together
   1: // Create the Container and Configure.
   2: MAC container = new MAC();
   3:  
   4: container.Register(() => new CupOfCoffee(
   5:                      new TeaSpoonOfSugar()));
   6: container.Register(() => 
   7:     new robcthegeek(
   8:         new BagOfChocolatePeanuts(),
   9:         container.Get<CupOfCoffee>()));
  10:  
  11: // Now Get a "robcthegeek" from the Container..
  12: // Note here we are not needing to spin up dependencies.
  13: var rob = container.Get<robcthegeek>();
  14: rob.GeekOut();

Easy! And if you look at the sample code there is not a lot to the “MAC”.

But, as said before, this is a real simplistic example. The code for the “MAC” is far from production-ready (e.g. there is no error checking in there to make sure types are registered). Also we could perhaps infer the factory methods based on default constructors and things like that. But remember, if you want a “proper” container, then go and get one, there are plenty out there (or start a new one!).

IoC Containers Worth Using

Even though the MAC is MAJOR AWESOME in its design, there are several others that are definitely worth your attention:

In Summation

Dependency injection and inversion of control help us to write much more loosely-coupled cohesive object model. While this brings about many benefits in the design and maintainability of our application, all is not rosy. A large amount of dependencies are naturally introduced, and these must be satisfied in order to get classes created. IoC containers help reduce the pain of this by providing a one-stop shop for us to define the dependency chain and then perform this process for us each and every time we need a class.

Very useful.

I will be reviewing the “IoC Containers Worth Using” in the near future.. Stay tuned.

1 comment:

  1. Great post, have to add two things though.

    The normal usage-scenario for an IoC container is that you register a service type as an implementation type. This separates interface from implementation. For example you register the RobCTheGeek type as an IGeek, then every time some part of the code needs an IGeek they get a RobCTheGeek instance.

    I've been trying out different containers. I have one to add to the list you already posted and that's Ninject. Not only is it the container with the coolest name but it's on par with the other containers when it comes to features and ease of use too. There's also version of it that works in silverlight.

    ReplyDelete