I have seen many articles on this topics. There are many articles on internet for this topic and it is always been a bit difficult to understand this concept. So here, I tried to explain this topic in my simple(dumb) language. I know, this is not the perfect way but I hope that example(s) used here will help us to understand this topic.To understand Dependency Injection, I think we first make ourself comfortable with following two concepts.

  • Dependency Inversion Principle (DIP)
  • Inversion of Controls (IoC)

Why Dependency Injection (DI)?

To understand why Dependency Injection is needed, first we should see DIP and IoC in detail. Then we will see what problem DI can solve.

Dependency Inversion Principle (DIP) :

Dependency inversion principle is a software design principle which provides us the guidelines to write loosely coupled classes. According to the definition of Dependency inversion principle:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend upon details. Details should depend upon abstractions.

Let’s try to understand this statements using example. Let’s assume that we want to write code for event logging. When some event is performed, we need to log event information in file. We will have one EventLogger class, which will be responsible to write log in simple plain log files. Here is our example.

   public class EventLogger
   {
       public void Log(string message)
       {
           //Logging logic here 
       }
   }
 
   public class EventHandler
   {
       //EventLogger object to write log
       EventLogger Logger = null;
 
       //This function will be called when logging is required
       public void DoLogging(string message)
       {
           if (Logger == null)
           {
               Logger = new EventLogger();
           }
           //Write log
           Logger.Log(message);
       }
   }

This code seems fine but there is a little problem with this design. This code violates the dependency inversion principle. i.e. the high level module EventHandler depends on EventLogger is a concrete class and not an abstraction.

We will see loopholes in this design when we will try to extend this code. Let’s add some more functionality. Now for a specific set of events, we want to write database log in our table. Till now we were just logging information in plain log files. Now, we we want to write some information in database. One way to implement this is, create a class EventDBLogger and create its object in EventHandler. But any moment we will be using one one object either EventDBLogger or EventLogger.

This problem will be even more difficult to solve when we add some more selective functionality like sending an email to user for some events. Then we will have to take care of one more class instance in EventHandler. The dependency inversion principle says that we need to decouple this system in such a way that the higher level modules i.e. the EventHandler in our case will depend on a simple abstraction and will use it. This abstraction will in turn will be mapped to some concrete class which will perform the actual operation. Let’s see, how to do this.

Inversion of Control (IoC) :

Inversion of Control defines how two modules should depend on each other. Inversion of control is the actual mechanism using which we can make the higher level modules to depend on abstractions rather than concrete implementation of lower level modules.

So to implement Inversion of Control in our problem scenario,  we need to create first an abstraction on which our higher level module can depend. So let’s create an interface on which our high level module EventHandler can depend on.

    public interface ILogger
    {
        void Log(string message);
    }

Now we will change our higher level module and will use this interface in place on lower level concrete class.

   public class EventHandler
   {
       //EventLogger object to write log
       ILogger Logger = null;
 
       //This function will be called when logging is required
       public void DoLogging(string message)
       {
           if (Logger == null)
           {
               //Map abstraction(interface) to concrete class
           }
           //Write log
           Logger.Log(message);
       }
   }

Now what should be design for our lower level classes? I think, you got the picture in your mind now. Yes, you are thinking correctly. Our lower level classes will implement this interface.

   public class EventLogger : ILogger
   {
       public void Log(string message)
       {
           //File logging logic here 
       }
   }
 
   public class EventDBLogger : ILogger
   {
       public void Log(string message)
       {
           //DAtabasee logging logic here 
       }
   }
 
   public class EventEMailLogger : ILogger
   {
       public void Log(string message)
       {
           //Email sending logic here 
       }
   }

Class diagram of this structure may look like this :

DependencyInjection

Now what we have done here is our high level modules are dependent only on abstractions and not the lower level concrete implementations, which is exactly what dependency inversion principle states.

But when we see our higher level class i.e. EventHandler, we can that there is still a missing link between interface object and instantiation of real lower level object. Let’s try something like this.

    public class EventHandler
    {
        //EventLogger object to write log
        ILogger Logger = null;
 
        //This function will be called when logging is required
        public void DoLogging(string message)
        {
            if (Logger == null)
            {
                //Map abstraction(interface) to concrete class
                Logger = new EventLogger();
            }
            //Write log
            Logger.Log(message);
        }
    }

And we are again on the first step where we have started! The concrete class creation is still inside the higher level class. We need to make it totally decoupled and we know that. Now question here is how to do that??!!!

Now the story begins here. 🙂 This is exactly where Dependency injection comes in picture. So its time to look at dependency injection in detail now.

Dependency Injection (DI) :

The things that I wanted to write at the start of this blog but that wouldn’t be a right place. 🙂 Now I can write it here.

Dependency Injection is mainly for injecting the concrete implementation into a class that is using abstraction i.e. interface inside. The main idea of dependency injection is to reduce the coupling between classes and move the binding of abstraction and concrete implementation out of the dependent class.

Dependency injection can be done in three ways.

  1. Constructor injection
  2. Method injection
  3. Property injection

Constructor Injection :

In this approach we pass the object of the concrete class into the constructor of the dependent class. Here, constructor of higher level class will have parameter and will instantiate interface object. Here is our example.

   //Constructor injection
   public class EventHandler
   {
       //EventLogger object to write log
       ILogger Logger = null;
 
       public EventHandler(ILogger Logger)
       {
           this.Logger = Logger;
       }
       //This function will be called when logging is required
       public void DoLogging(string message)
       {
           //Write log
           Logger.Log(message);
       }
   }

Let’s assume that there is one method to print document and we want to write file log in this method. So to do this, we can write code as below and pass concrete object of EventLogger class.

       public void PrintDocument()
       {
           EventLogger Logger = new EventLogger();
           EventHandler Handler = new EventHandler(Logger);
           Handler.DoLogging("Constructor Injection : This is print document file log");
       }

Now if we want to write database log for some event then we just need to pass object of EventDBLogger class and rest of code is just the same. This same object will be used for entire lifecycle as we have injected it from constructor.

Method Injection :

We just saw that constructor injection will be used when we want the dependent class to use the same concrete class for its entire lifetime. Here in case of Method Injection, we can pass separate concrete class on each invocation. Let’s get into code to understand this.

    public class EventHandler
    {
        //EventLogger object to write log
        ILogger Logger = null;
 
        //This function will be called when logging is required
        public void DoLogging(ILogger ConcreteLogger, string message)
        {
            //Write log
            this.Logger = ConcreteLogger;
            Logger.Log(message);
        }        
    }

Here, DoLogging method will take concrete object of ILogger and will bind it to the instance of interface.

        public void PrintDocument()
        {
            EventLogger ConcreteLogger = new EventLogger();
            EventHandler Handler = new EventHandler();
            Handler.DoLogging(ConcreteLogger, "Method Injection : This is print document file log");
        }

On the same line, we can pass any object here to DoLogging method for file log, email log or db log.

Property Injection :

Here concrete object will be injected with the help of property. Here is the code.

    public class EventHandler
    {
        //EventLogger object to write log
        ILogger Logger = null;
 
        public ILogger LoggerProp
        {
            get { return Logger; }
            set { Logger = value; }
        }
        //This function will be called when logging is required
        public void DoLogging(string message)
        {
            //Write log
            Logger.Log(message);
        } 
    }

Here, setter action of property will take care to assign concrete object to interface instance of the class.

    public void PrintDocument()
    {
        EventLogger ConcreteLogger = new EventLogger();
        EventHandler Handler = new EventHandler();
        Handler.LoggerProp = ConcreteLogger;
        Handler.DoLogging("Property Injection : This is print document file log");
    }

Same way we can pass any object for file log, email log or db log.

When to use which injection method?

Now we have discussed all scenarios. So here is summary when to use which injection method.

Constructor Injection : When we want to use one object of concrete class for the entire lifetime.
Method Injection : When we want to pass separate object for each invocation.
Property Injection : When selection of concrete class and invocation of method are in separate places.

 

References :

Advertisements