Inversion of Control (IoC)
In the field of software development, the terms IoC (Inversion of Control) is frequently used, especially in the context of object-oriented programming. However, this concept can be a bit confusing for some people who are not very familiar with it and they might be curious that:
- What actually the IoC is?
- How can we use IoC?
- What are the benefits of using it?
So the goal of this article is to get answers of these queries by exploring the concept IoC with different ways to implement it in software development. Let’s get start!
What is IoC?
Inversion of Control is a principle in software engineering which transfers the control of objects or portions of a program to a container or framework.
Let’s try to understand this with some real life example. Suppose your professor tells you that there is a conference happening in another city and you have three hours to reach and present a topic there, and the route to that city is also three hours long. You haven’t prepared for the topic beforehand, so you need to prepare the presentation and also drive to the other city in that time. Both of these tasks will be difficult for you to do together, so instead of driving yourself, you book a ride and go with the driver. Now you don’t have to worry about the driving and choosing the best route to reach there on time, as it is the rider’s responsibility to get you to your destination on time.
In this example, you have two tasks — preparing a presentation and driving to another city, both of which require your attention and time. However, instead of doing both of these tasks yourself, you delegate the task of driving to a ride-sharing service. By doing this, you are transferring the control of the task of driving to the ride-sharing service. This is similar to how IoC works in software development, where the control of object creation and management is delegated to a framework or tool, allowing the developer to focus on the main task at hand.
Let’s look at simple example of the traditional approach where the flow of control is tightly coupled, meaning that if a component needs to use another component or class, it must explicitly create or instantiate that object and maintain its lifecycle.
public class Teacher {
private Course course;
public Teacher(Course course) {
this.course = course;
}
public void assignCourse() {
System.out.println("Assigned course: " + course.getCourse());
}
}
public class Course {
private String course;
public Course() {}
public Course(String course) {
this.course = course;
}
public String getCourse() {
return course;
}
}
In the above example, class Teacher needs to call the getCourse method of class Course to complete its functionality, it means class Course is a dependency of class Teacher. In order to provide the dependency, class Teacher creates and instantiates the object of class Course itself, so class Teacher is responsible for maintaining the lifecycle of Course object.
Now try to use IoC in the above example
public class Teacher {
private Course course;
public Teacher() {
this.course = ObjectContainer.getObjectOfCourse();
}
public void assignCourse() {
System.out.println("Assigned course: " + course.getCourse());
}
}
public class Course {
private String course;
public Course() {}
public Course(String course) {
this.course = course;
}
public String getCourse() {
return course;
}
}
public class ObjectContainer {
public static Course getObjectOfCourse() {
return new Course();
}
}
As in the above code, the flow of control is transferred to the external class named ObjectContainer, now instead of creating and maintaining the object of class Course itself, class Teacher uses the getObjectOfCourse method of ObjectContainer class to get the object. In simple, IoC says that the dependencies of a component are managed by an external framework or container and that container is responsible for creating, managing and destroying the dependent objects and the component does not have to worry about them. The component simply declares its dependencies, and the framework takes care of providing them.
What are the different ways to implement IoC?
Though there are several ways to implement Inversion of Control (IoC) in software development. Here are some of the common approaches:
- Dependency Injection (DI): Dependency Injection (DI) is the most popular way to implement IoC. In DI, the dependencies of a component are “injected” into it by an external framework or container. This means that the component does not need to be responsible for managing its dependencies, and can simply declare what dependencies it requires. There are three types of DI: constructor injection, setter injection, and field injection. Will share detailed implementation of DI in next article.
- Service Locator: Service Locator is another popular technique to implement IoC that uses a central registry or service locator which maintains references to all the objects that a component might need to use. The component then queries this registry to obtain the required dependencies. In this approach, the service locator is responsible for creating and managing the lifecycle of the dependencies.
- Factory Method Pattern: This is a design pattern that uses a factory method to create objects. In this approach, the object creation is delegated to a factory method, which can be overridden by subclasses to provide a different implementation of the object creation process.
Why should you use IoC?
There are a number of benefits of using IoC but one of the key advantages is that it provides loose coupling between components. In a traditional program, changes to a single component can have a ripple effect throughout the entire application. However, with IoC, components are decoupled, meaning that changes to one component have a minimal impact on other components. This makes it easier to maintain and evolve complex applications over time.
Some other benefits of IoC are:
- It allows developers to write code that is more reusable and easier to maintain.
- When there is a tight coupling between components, testing one component can require running the entire application. With IoC, components can be tested in isolation by mocking the dependencies, which allows developers to run tests more quickly and focus on a specific component or feature without worrying about the behaviour of other components.
Conclusion
In conclusion, Inversion of Control (IoC) is a powerful design pattern that allows us to separate the construction and use of objects in your application. With IoC, you can create loosely coupled code that is more flexible and easier to maintain, as it promotes the separation of concerns and allows for easier testing and reuse of code.