-->

10/10/2011

Dependency Injection (DI / IOC)


"Loosely Coupled" is the one of the basic qualities of a good design.
Let me justify that first.
If you have 2 choices:
1. A task which has lot of dependencies on others.
2. A task which have Minimum number of dependencies.
Which one will be easier. Obviously its #2.
So, less dependencies means more loosely coupled and better the design it will be.

I believe 80% of world population know below Guy.
He is Harry potter with a Magic Wand (stick in his hand), which he will be using to cast the spell.
Now lets watch the potter's magic in a console application using c#.

namespace DISample
{
    class Program
    {
        static void Main(string[] args)
        {
            WoodenMagicWand wand = new WoodenMagicWand();
            HarryPotter HP = new HarryPotter();
            HP.CastSpell(wand);
            Console.ReadKey();
        }
    }

    public class WoodenMagicWand
    {
        public string Type { get { return "Wooden"; } }
    }

    public class HarryPotter
    {

        public void CastSpell(WoodenMagicWand wand)
        {
            Console.WriteLine("Harry Potter casted the spell with his " + wand.Type + " Wand");
        }
    }
}
The out put will be :
Now, lets imagine tomorrow we have a new kind of wand , say "Metallic wand". Identify the places where we need to change the code. This is only one dependency that we are seeing. Imagine what if you have 3 or 4 such dependencies.
The  code we wrote above is a tightly coupled and will involve lot of maintenance efforts.

"Dependency Injection(DI)" means that objects should only have as many dependencies as is needed to do their job - and the dependencies should be few. Furthermore, an object's dependencies should be on interfaces and not on "concrete" objects, when possible. "DI" also cryptically known as "Inversion of Control" (IoC), can be used as a technique for encouraging this loose coupling.

DI can be achieved by 4 different ways.

#1. Constructor Injection: 
Constructor Injection is the DI technique of passing an object's dependencies to its constructor.
namespace DISample
{
    class Program
    {
        static void Main(string[] args)
        {
            IWand wand = new MetalicMagicWand();
            HarryPotter HP = new HarryPotter(wand);
            HP.CastSpell();
            Console.ReadKey();
        }
    }

    public interface IWand
    {
        string Type();
    }
    public class WoodenMagicWand:IWand
    {
        public string Type() {  return "Wooden";  }

    }

    public class MetalicMagicWand:IWand
    {
        public string Type() { return "Metalic"; }

    }

    public class HarryPotter
    {
        private IWand _wand;

        public HarryPotter(IWand wand)
        {
            this._wand = wand;
        }

        public void CastSpell()
        {
            Console.WriteLine("Harry Potter casted the spell with his " + _wand.Type() + " Wand");
        }
    }
}
Above code has been written exactly following the definition of DI. Harry Potter object depends on Wand object to perform a task. So, instead of hard coding the concrete class initialization , we injected the dependency object at the time of constructor. It is clear to the developer invoking the object which dependencies need to be given to the "Harry Potter" object for proper execution.
If you observe the code above, the only change you need to do to switch between different wands is highlighted in yellow.

#2. Setter Injection:
Setter Injection does not force dependencies to be passed to the constructor. Instead, the dependencies are set onto public properties exposed by the object in need. As implied previously, the primary motivators for doing this include:
  1. supporting dependency injection without having to modify the constructor of a legacy class, and
  2. allowing expensive resources or services to be created as late as possible and only when needed.
namespace DISample
{
    class Program
    {
        static void Main(string[] args)
        {
            IWand wand = new MetalicMagicWand();
            HarryPotter HP = new HarryPotter();
            HP.Wand = wand;
            HP.CastSpell();
            Console.ReadKey();
        }
    }

    public interface IWand
    {
        string Type();
    }
    public class WoodenMagicWand:IWand
    {
        public string Type() {  return "Wooden";  }

    }

    public class MetalicMagicWand:IWand
    {
        public string Type() { return "Metalic"; }

    }

    public class HarryPotter
    {
        private IWand _wand;

        public IWand Wand
        {
            get {_wand==null?throw new MemberAccessException("Error: Object Not Initialized");:return _wand; }
            set { _wand = value; }
        }       

        public void CastSpell()
        {
            Console.WriteLine("Harry Potter casted the spell with his " + _wand.Type() + " Wand");
        }
    }
}
In the above example, the constructor accepts no arguments. Instead, the invoking object is responsible for setting the Wand object dependency before the method CastSpell() is called.
Basically Setter is preferred for situations where you are implementing Loose coupling in an existing code.
There are a few cons in using Setter DI.
  1. Does not make it clear to the developer which dependencies are needed when, at least until a "has not been initialized" exception is thrown, and
  2. Makes it a bit more difficult to track down where the exception came from and why it got thrown. With this said, Setter Injection can save on modifying a lot of legacy code when introducing new methods, and can provide a performance boost if the dependency is expensive or not easily accessible.
#3. DI Controller:
The "DI controller" approach is the simpler to understand and implement. In a properly tiered architecture, an application has distinct layers for handling logic. The simplest layering usually consists of a data-layer for talking to the database, a presentation-layer for displaying the UI, and a domain-logic layer for performing business logic. A "controller" layer always exists, even if not well defined, for coordinating UI events to the domain and data layers, and vice versa. For example, in ASP.NET, the code-behind page acts as a rudimentary controller layer. Regardless of what you use as your controller, the controller is an appropriate location for performing Dependency Injection "wiring". This is where concrete objects are created and injected as dependencies.
One of the major benefits of using a DI-controller to inject dependencies is that it's straightforward and easy to point to where the creation is occurring. The drawback to using DI-controllers is that the dependencies are still hard-coded somewhere; albeit, they're hard-coded in a location that is often subject to frequent changes anyway.

#4. DI Service Locator:
An alternative to using Dependency Injection is to use a Service Locator to fetch the dependency objects.  Using a Service Locator creates a level of indirection between a class and its dependencies. If you boil down, everything will come to a factory service sufficing the injection mapping based on the type of object required.


Hope Harry potter's Spell worked a bit. :)

Is it helpful for you? Kindly let me know your comments / Questions.

3 comments:

  1. Good to read Pratap.. not sure if you remember me... But i do remember you and cherish my memories at Deloitte as well :) Alas, life and almighty had other things planned for me ;)

    Will follow your blog. :)

    S.V. Sita Kiran

    ReplyDelete
  2. just to understand would it harm or defeat the purpose, if we make the parameter of castspell based on Interface and remove the concrete implemetation like......

    public void CastSpell(IWand wand)
    {
    Console.WriteLine("Harry Potter casted the spell with his " + wand.Type + " Wand");
    }

    ReplyDelete
  3. In the above example quoted, the wand type is already induced into Harry potter class using constructor injection. so,no need to pass it as a parameter exclusively.
    Secondly,the wand is of IWand and it is not concretely implemented.
    Thanks for your suggestion/comments.

    ReplyDelete