Dependency Injection & IOC - Inversion of control explained in depth

Dependency Injection & IOC - Inversion of control explained in depth

Intro to Inversion of Control and Dependency Injection

Introduction

Dependency Injection (DI) and Inversion of Control are two terms that, although related, are commonly confused and misinterpreted. In this article, you will attempt to explain these 2 essential concepts with a real-time example. First, let us see the definitions & then move to a practical part.

Definitions

Inversion of Control :

Inversion of Control is a principle in software engineering that transfers the control of objects or portions of a program to a container or framework. We most often use it in the context of object-oriented programming.

Dependency Injection :

Dependency Injection is a software design technique in which the creation and binding of dependencies are done outside of the dependent class.


Story begins

Let's assume you are working on a payments-related application. so basically, you have to send OTP through mail or some SMS service provider. it will be the best analogy to understand the dependency injection. The typical approach will be something like the code below.

//Your service class
public class SMSService {

    public void sendOTPThroughGateway(String message, int otp) {
        //logic to send OTP through SMS service provider
        System.out.println(message+otp);
    }

    public void sendOTPThroughMail(String message, int otp) {
        //logic to send OTP through Mail
        System.out.println(message+otp);
    }
}
// Your main class
public class Main {
    public static void main(String[] args) {

        SMSService service = new SMSService();
        service.sendOTPThroughGateway("you OTP",5656);

    }
}

The above implementation is not wrong but there are a few issues:

Suppose In the future, your client or manager wants to send SMSs through some other third-party service instead of using an SMS gateway or Mail. To achieve this we will end up changing the concrete implementation of (SMSService) with another implementation like below.

public class SMSService {
    public void sendOTPThroughGateway(String message, int otp) {
        //logic to send OTP through SMS service provider
        System.out.println(message+otp);
    }

    public void sendOTPThroughMail(String message, int otp) {
        //logic to send OTP through Mail
        System.out.println(message+otp);
    }

    public void sendOTPThrough3rdParty(String message, int otp) {
        //logic to send OTP through Third Party service
        System.out.println(message+otp);
    }
}

What's wrong with this approach?

We are losing flexibility and are forced to rewrite the code in this case. We’ll end up mixing the responsibilities of classes, our (Main) Class should never know about the concrete implementation of (SMSService), this should be done in a different way from the classes using “Interfaces”.

When this is implemented, it will give us the ability to change the behavior of the system by swapping the (SMSService) used with another N number of new service that implements the same interface.

The good approach

To fix the above issues we can use Interfaces which will be implemented by our SMSService and the new ThirdPartySMSService & MailSMSService, basically, the new Interface (SMSInterface) will expose the same behaviors of both services as the code below:

// SMSInterface
public interface SMSInterface {
    public void sendSms(String message,int otp);
}

This interface is implemented by the other 3 SMS service provider with their own business logic. The Service class code seems like below:

//SMSService 
public class SMSService implements SMSInterface{
    public void sendSms(String message, int otp) {
        /*logic to send OTP through SMS service provider
          with the own way of implementation.
        */
        System.out.println(message+otp);
    }
}

//MailSMSService
public class MailSMSService implements SMSInterface{

    public void sendSms(String message, int otp) {
        /*logic to send OTP through Mail service provider
          with the own way of implementation.
        */
        System.out.println(message+otp);
    }
}

//ThirdPartySMSService
public class ThirdPartySMSService implements SMSInterface{
    public void sendSms(String message, int otp) {
         /*logic to send OTP through 3rd-party provider
              with the own way of implementation.
        */
        System.out.println(message+otp);
    }
}

We have achieved a lot of flexibility and implemented the separation of concerns in our code.so, now we can call different services as per business needs from a main class. The code seems like below.

public class Main {
    public static void main(String[] args) {

    //General SMS service
    SMSService service = new SMSService();
    service.sendSms("your OTP",1412);

    //General 3rd party service
    ThirdPartySMSService partyservice = new ThirdPartySMSService();
    partyservice.sendSms("your OTP",1412);

    }
}

But still, we need to do a change to the code base to switch between the three SMS Services. Here where a technique called Dependency Injection helps us. So we need to implement Dependency Injection.

To achieve this, we need to implement a (SMSWrapper) class. The Main class constructor passes the dependency through it, by doing this, the code which uses the (SMSWrapper) class to determine which concrete implementation of (SMSService) to use.

//SMSWrapper
public class SMSWrapper {
    private  SMSInterface smsInterface;

    public SMSWrapper(SMSInterface service){
      smsInterface = service;
    }

    public void processOTP(String message,int otp)
    {
        smsInterface.sendSms(message,otp);
    }
}
public class Main {
    public static void main(String[] args) {
        //General SMS service
        SMSService service = new SMSService();
        SMSWrapper serviceWrapper = new SMSWrapper(service);
        serviceWrapper.processOTP("your OTP",1412);

    }
}

Now, The Main class is not responsible for determining the implementation class. This means we have inverted the control, the (Main) class is no longer responsible to decide which implementation to use, and the calling code does. We have implemented the Inversion of Control principle.


Inversion of Control : (IOC)

Inversion of control (IoC) is a design pattern in which we declare an action to be taken when a certain event happens in our system. It is heavily used in software because it allows us to write clean and maintainable code. As we have seen in the above example.

Inversion of Control(IoC) is a principle by which the control of objects is transferred to a container or framework. which in simple terms handles the object creation at a higher level, so we can access the object instance without manually initializing It.

Conclusion

As mentioned above, Inversion of Control(IoC) helps in the creation of loosely coupled applications because of Dependency Injection. There are several more ways to achieve the same behavior. Follow & share your comments on this article for more content like this.

Did you find this article valuable?

Support Saravana Sai by becoming a sponsor. Any amount is appreciated!