What Is the Power of Inversion of Control?
Inversion of control is a principle in Software Development that sometimes could lead to some confusion.
In this article, I’ll explain what Inversion Of Control means, the power of this principle, the benefits of it, and the close relation with Dependency Injection.
This principle states that you should invert the flow of control within your modules, by using another concept that is Dependency Injection. That way we can invert the control by injecting dependencies into your classes and making them work with different types of objects.
This in turn allows your superclasses and subclasses to depend on abstractions and not in concrete implementations, which basically means that we’re also following the Dependency Inversion principle. Classes are more flexible and they only focus on interface definitions, and not in initialization processes.
IoC is about being able to decouple your classes and make your code DRY, one of the main benefits of IoC is that your code is more readable because of the abstraction of components.
There are many ways of implementing IoC, but one of the most common ways is through Dependency Injection.
Dependency Injection is about injecting dependencies into another class so that the class can function correctly, but what is really a dependency?. Well when a class relies on other methods from other objects then, that class has a dependency, because it can’t do its job without this other object.
So by using Dependency Injection, we are able to invert the flow of control and decide what type of dependency we pass to a certain component, and as long as the dependency follows a common interface we can pass a variety of different objects. This way our components are loosely coupled, and more flexible, also it is quite easier to test because now the component does not initialize any other object in the constructor, but instead it receives the dependency as a param, either through the constructor method, setter method or in any other method within the class.
There are three types of Dependency Injection:
1. Constructor Injection
With this type of Dependency Injection, the dependencies are passed to the object on the constructor method, so that the class does not need to create any objects. Constructor injection is quite common and powerful because it allows injecting the dependencies when the object is created. The important part of constructor injection is to make sure you only pass the dependencies that the class needs in order to work correctly. And not pass any unnecessary dependencies.
2. Setter Injection
In this case, the dependency is injected via a setter method inside the class. This type of Dependency Injection allows injecting dependencies after you initialize the object. This can be quite useful when we don’t have the dependency ready to be injected when we initialize a class, but rather the dependency can be injected later using this setter method. This also allows the class to work with a default implementation of the setter object, and if at any point we decide to change that dependency, we can use the setter method. This type of Dependency Injection is similar to the strategy pattern where we can pass the strategy in the constructor method, but also we can use setter injection to change the strategy after we initialize the class. I personally use setter injection when I want to switch to use a different type of the same dependency within my class.
3. Method Injection
With this use of Dependency Injection, we inject the dependency when we call the method. It is really common to use it when the dependency can be different every time we call a method. As opposed to constructor and setter injection, we use method injection when the dependency object could vary. The method only knows that the dependency will respond to certain methods and properties. For instance, we can use method injection to inject a different type of factory, we know that all factories will respond to the same methods but the method itself allows the caller to inject that factory.
Now that we know the different types of Dependency Injection, we can see that by implementing these techniques we can invert the control of the program by injecting dependencies, and that is pretty much the concept of IoC.
Here is an example:
This is a basic example of IoC. Here the grade service class is receiving in the constructor method the required dependencies for this service to work. In this case, is the user and notifier objects. So when we initialize this class we have to pass these two dependencies to the class. In the call method, we use the notifier object and call the notify method. This way the class can work with different types of the notifier object as long as all notifier instances follow the same interface.
This way we have much more control over this class and how it works. Because the caller can decide which objects to pass into when initializing the service, thus allowing the caller to have control over which notifier to use depending on the user.
Now we can use this service to calculate the user’s grades and notify them using different notifier objects.
This also allows us to test this class really easy because we can mock the dependencies.
This is a more elaborated example of how to implement inversion of control. In this example, we have an HttpRequester class, which we can use to send HTTP requests to other microservices, APIs, or external sources.
In this class, we use constructor injection to inject the dependencies this class needs to send the request. For instance, we inject the params, user, provider, and strategy.
We also have a setter method that allows us to change the type of strategy we use to set the headers and to get the credentials. This is a setter injection in action. Since we can send both internal and external HTTP requests, we need to set headers and credentials differently, so here we can use setter injection because the strategy will not change every time we call this class, but rather in some cases we will need to set the strategy to another object, which is a perfect situation to use setter injection.
Then each strategy class implements a common interface which allows the HttpRequester class not to worry about calling different methods depending on the strategy. But rather just call the set_headers method because both strategies follow the same interface, this is why interfaces are so powerful.
This way both the higher-level classes and lower-level classes depend on abstractions and not on concrete implementations. This basically means that we are also following the Dependency Inversion principle.
Lastly, each strategy uses method injection to receive the request object for the set_headers method. Since this object is unknown when we initialize the HttpRequester class, we can use method injection to inject the request object. In the case of the second method, we inject the provider object because the strategies do not have access to this object when we initialize them, which also indicates that method injection is a perfect fit.
With this implementation, we invert the flow of control, so that we can control how this class works and have much more options and flexibility.
This also allows us to test these three classes really easily because we can mock the dependencies that we are injecting. It gives us the option to extend this code without modifying the existing one. For instance, we could introduce a new strategy class, without having to modify the HttpRequester class.
That is what Inversion Of Control is all about, making your components loosely coupled, your code more flexible and reusable. And it gives you the power of controlling the flow of your classes and components.
I hope you have now a more clear idea of what is IoC, how to apply it, and when to use it.
I hope you enjoyed this article and thank you for reading it!. Stay tuned.