Understanding Inversion of Control (IoC) Principle
The biggest issue, any enterprise app can face is the complexity of wiring different elements or components together. Over a long period of time, a number of frameworks have been created to provide a facility to assemble and bind these elements from different layers. These frameworks are commonly known as Containers and some of the very popular examples of such frameworks are PicoContainer and Spring. There are various interesting design principles used inside these containers and Inversion of Control is one such principle. In this article, we will understand what Inversion of Control exactly is and how it is used in a modern-day enterprise application.
What is IoC???
As the name suggests, Inversion of Control Principle (it’s a principle, not a design pattern) is basically about inverting the control flow of a program. This term was popularized by Stefano Mazzocchi in a defunct Apache Software Foundation project, Avalon, then further popularized in 2004 by Robert C. Martin and Martin Fowler.
As Martin Fowler said, “Inversion of control is a common characteristic of frameworks, so saying that these lightweight containers (spring and pico container, etc.) are special because they use inversion of control is like saying my car is special because it has wheels.” It is basically a defining characteristic of a framework. Inversion of Control is used to increase the modularity of the program and makes it extensible. But the question still is, “What aspect of control are they really inverting?”
In early days of computer software, the command-line was used for general programs and thus those user interfaces were controlled by the application itself. In such programs, you directly control the flow of the program by entering the response into the command-line but in the GUI program, you basically hand over the control to the window system (UI framework) which then decides what to do next. Here the main control of the program moved away from you to the UI framework. Inversion of Control is a key part of what makes a framework different from a library. A library is essentially a set of functions that you can call, these days usually organized into classes. Each call does some work and returns control to the client whereas a framework does some work for you, you just need to plug your behavior into some places by either by subclassing or by plugging into your own classes. Then framework will call your code when it is required.
Note: Do not confuse it with “Dependency Inversion Principle”. DIP is a completely different principle which also does the job of providing loose coupling between classes and inverting the flow of dependencies. (Will write an article on it soon.)
Let’s now discuss how Inversion of Control can be implemented in the various programs. There are several basic design patterns which are used to implement IoC in object-oriented programming.
- Using a service locator pattern
- Using dependency injection pattern
- Using a contextualized lookup
- Using template method design pattern
- Using strategy design pattern
In all of the above patterns, Service Location (SL) and Dependency Injection (DI) are the most widely used patterns. In this article, we will understand the difference between DI and SL. In the further article of this series, we will look into these patterns deeply and learn how to write such patterns. Don’t confuse SL and DI, they both are completely different and the flow of the code too.
Let’s take an example
To understand Inversion of Control and it’s implementation by using dependency injection and service locator pattern, we take an example, a very simple one. In this following example, we have a component called “Library” which is used to return a list of books authored by a specific author. The code is self-explanatory and very simple for anyone to understand.
It gets all the books from a
finder object and then just checks if it is written by a particular author or not. The whole catch here is the
finder object and how we basically connect it with the
Library component. Reason why we are interested in this
finder object is because we want
booksAuthoredBy() to be completely independent of the implementation of
Finder . This
Finder can be remote implementation which fetches books from a server or it can also be a local database implementation which fetches books from a locally stored database. This can easily be done by creating an Interface
BookFinder and then providing its implementation.
So far so good as I know the
finder and I know it’s implementation. But we are making it as a framework and it must be extensible and usable by others too. Now think, if you are a college library manager and you want to use this framework for your college but you have your own database of books which is stored on some other server in a different way. You want to provide your own implementation of
BookFinder rather than using my implementation.
If you look at the above code and the diagram, you will notice that our
Library class depends on both the
interface BookFinder and it’s implementation
class BookFinderImpl (
RemoteBookFinder in the above example). As we are making it as an extensible framework, it would be better it depends only on the interface, not on the implementation details of it. But then how would get the instance to work with??? Now it’s the time to use Inversion of Control principle into a real system.
As mentioned above, there are various ways in which IoC can be implemented and used. Let’s take a look at both Dependency Injection and Service Locator.
Dependency Injection (aka D. I.)
The overall idea of Dependency Injection is to have a separate object, an Assembler which can then populate various fields by different instances or implementation by injecting those into the class. Now our dependency diagram looks like this -
It can be seen that now our
Library class only depends on
BookFinder i.e. the interface. The internal implementation of the Assembler is out of the scope of this article and will be discussed in the next part of this article. There are three styles popular for doing DI in such frameworks.
- Constructor Injection
- Setter Injection
- Interface Injection
Setter and Field Injection using Dagger
In the above code snippet, you will see that
class Library doesn’t know anything about the implementation of
interface BookFinder. All work of assembling and providing dependencies is being done by Dagger which is a compile-time DI framework for Java & Android. (Internals of Dagger will be discussed in the upcoming articles)
Service Locator (aka SL)
Dependency Injection (DI) allows us to make our Library extensible so that anyone can provide the suitable implementation of
BookFinder based on their needs/use-cases. But DI is not the only way to break this dependency, SL is such another technique to implement Inversion of Control.
The idea behind a service locator is to have an object that knows how to get all of the services that an application might need. So in our implementation of the Library Framework, Service locator would have a method that returns a
BookFinder when one is needed. Now our dependency diagram will look like this
Let’s take a look at how a simple Service Locator can be implemented. In our implementation, we are going to use a single instance of locator class.
Difference between Dependency Injection and Service Locator
Though both the approaches solve the fundamental problem of large enterprises applications ie. decoupling the dependencies from each other and in both the cases our application code (
class Library) is independent of the correct implementation of the
interface BookFinder . However, in the Service Locator pattern, application explicitly requests the dependencies by sending a message to the locator but in Dependency Injection, the dependency just appears without asking explicitly.
Dependency injection can help make it easier to see what the component dependencies are and it also makes testing easy. With dependency injector, you can just look at the injection mechanism, such as the constructor, and see the dependencies. With the service locator, you have to search the source code for calls to the locator. While doing testing, you can easily provide the mock implementation of your actual classes in DI pattern. There is not much difference between Dependency Injection or Service Locator pattern. So it just depends on your use-case and how well you are familiar with a particular framework. But as Martin Fowler said —
The key difference is that with a Service Locator every user of a service has a dependency to the locator. The locator can hide dependencies to other implementations, but you do need to see the locator. So the decision between locator and injector depends on whether that dependency is a problem.
He then further says that —
So the primary issue is for people who are writing code that expects to be used in applications outside of the control of the writer. In these cases even a minimal assumption about a Service Locator is a problem.
That’s all about the Inversion of Control for this article. There are few more topics and detailed articles to come on this topic and I will write them in upcoming days.
I hope you have enjoyed reading this article and if you do kindly show some ❤ by clapping and sharing it with your fellow devs. Every clap is appreciated and motivates me to write more. Ask any doubt/suggestion/correction in the comment section below or Tag/DM me on twitter.
Follow me on here to receive further updates related to this and upcoming articles. Follow me on Twitter — https://twitter.com/iamitkma. Thank you very much for reading so far. ❤❤❤ Much love ❤❤❤