The less heard type of Inversion of Control

Rahul Saha
Javarevisited
Published in
3 min readApr 20, 2022

Inversion of Control is a design principle that helps us write loosely coupled code. Whenever we hear IoC we automatically think about Dependency Injection and often these two terms are used interchangeably. However, that is not the case. And this prevalent misconception motivated me to write this article.

Photo by Nadine Shaabana on Unsplash

What exactly is Inversion of Control?

IoC is a principle that inverts the control flow of a program. It can be about creation of a complex object, writing callbacks, declarative coding and many other things.

While IoC is a principle, Dependency Injection is an implementation of that principle. Let's quickly see an example.

class OrderService{
OrderService(){
this.emailService = new EmailServiceImpl()
}
}

Here OrderService requires an EmailService to fulfil its business logic. However, there is a problem, too much coupling. Not only OrderService is dependent on a concrete EmailService class, it also has proudly taken the responsibility of creating the EmailService object (which in itself could be a complex, expensive operation).

To solve this issue, we can invert the control on EmailService object creation.

class OrderService{
OrderService(IEmailService emailService){
this.emailService = emailService;
}
}

Notice how OrderService no longer controls how EmailService is created. And as a bonus OrderService can now only depend on an abstraction (IEmailService being an interface) and we can choose the actual implementation even in runtime. With this, we can achieve much lower coupling and better testability.

The above solution is indeed dependency injection. Here, the dependency (EmailService) is injected into the subject (OrderService).

But dependency injection is not the only kind of IoC.

Yoda says there is another IoC pattern

Service Locator

Service Locator is another design pattern that uses Inversion of Control. However, unlike DI, dependencies are not injected into the subject. Dependencies are fetched by the subject on demand through a central registry.

An Example: OrderService sends notification to user depending on user preference. Notification can be pager, SMS or email (default).

class OrderService{
void notify(User user){
serviceLocator.getServiceBy(user.preference())
.sendNotification(user);
}
}
class ServiceLocator {
INotificationService getServiceBy(
NotificationType notificationType){
return switch(notificationType){
case PAGER -> pagerService
case SMS -> smsService
else -> emailService
}
}
}

Just like DI here also OrderService does not take responsibility of creating the EmailService (or any other service), hence low coupling is achieved.

However, another wonderful thing is achieved here through ServiceLocator. OrderService does not need to know about any sub interfaces of NotificationService, also its job of finding appropriate NotificationService for a notificationType has been delegated to service locator which can be reused.

If we implement this with DI OrderService should have dependency on all type of NotificationService and it could not delegate the job of finding appropriate NotificationService.

Drawbacks of ServiceLocator

  1. Dependencies are not obvious and more prone to error due to missing dependencies in runtime.
  2. All subjects must hold a reference to the service locator.
  3. Harder to test compared to DI

Conclusion

As we use DI every day, we should understand the subtle difference between DI and IoC, and also the relation between them. Although DI is a more appropriate choice for most of the use cases, there are scenarios where Service Locator shines.

--

--

Rahul Saha
Javarevisited

A software engineer from Kolkata, India. Also a linux enthusiast, photographer and accidental drawing artist.