Inversion of Control

In this blog, I will create a sample showing how using IoC containers can help achieve a good design - mainly in reducing dependencies.
In the code below, I will create a hierarchy of object relationships. Level 3 objects use Level 2 objects and Level 2 objects need Level 1 objects. If say, there are 2 classes at each level (in my example defined as A1, B1, A2, B2 etc). Consider these classes as 2 different behaviors at each level. And a behavior can be used based on certain conditions. To meet these requirements without using any sort of inversion of control, we will be adding too many dependencies at each level. Also, when a new behavior is added, we will need to modify much more code to make it all work.

If we use IoC, we can write much cleaner code where the dependencies are easy to manage. Let's take a look at the code.

Level 3 object gets instantiated first. Its created based on a string name in the Wireup method. While instantiating, it sees that it needs a level 2 object in the constructor. Level 2 object is created by type in the Wireup method. Creating by type means that Autofac (the IoC container) knows it can automatically wire this dependency - so a level 2 object gets created and passed in the constructor. After this, we create another dependency PropertyInjectionDemo, this time by using property injection. In the wireup method, for A3 and B3 we had used PropertiesAutowired() method - this tells autofac to create the property dependencies if they are registered.

After the initiation is done, we call a method on Level 3 object which calls a method on level 2 object we created as part of initiation. The level 2 method then needs a level 1 object based on a condition it received. Here we resolve this dependency by utilizing the condition and key.

We can also see how it helps if we program to interfaces instead to concrete implementations.

When the lifetime scope is going out of scope (end of using statement), it calls the Dispose on all the objects it created thus cleaning up after the job is done.

Circular Dependencies: The circular dependencies can be handled by providing the argument PropertyWiringOptions.AllowCircularDependencies in the PropertiesAutowired() method. Autofac supports property-property and constructor-property circular dependencies. It does not support constructor-constructor dependencies.

The code also uses strategy pattern. Strategy pattern enables us to use different behaviors at runtime. Let's say that A2 and B2, both of which implement ILevel2 are different behaviors. A3/B3 has the property for ILevel2. At runtime, we can use either of A2 or B2 behavior.

Results are shown below: