Home » Inversion of Control (IoC)

Inversion of Control (IoC)

by aryalspace
6 minutes read

Inversion of Control (IoC) is a principle in software engineering where the control flow of a program is inverted compared to traditional procedural programming. Instead of the program dictating the flow of control, it delegates this responsibility to a framework or container. This allows for more flexible and decoupled code.

To understand IoC, let’s imagine a simple scenario involving a guitar player and a concert:

Traditional Approach

In a traditional approach, the guitar player would be responsible for organizing everything:

  1. Finding a venue.
  2. Promoting the concert.
  3. Selling tickets.
  4. Performing.

In this case, the guitar player has full control over all aspects of the concert.

IoC Approach

With IoC, the guitar player delegates many responsibilities to a concert organizer:

  1. The organizer finds the venue.
  2. The organizer promotes the concert.
  3. The organizer sells tickets.
  4. The guitar player only needs to focus on performing.

Here, the control is inverted: the concert organiser (the framework or container) manages the overall flow, and the guitar player (the specific component) performs a specific role when called upon.

Practical Example in Software Development

Imagine a software application that sends notifications. In a traditional approach, the notification service might directly instantiate and manage the email and SMS services:

Scenario

Imagine we have a NotificationService that needs to send notifications via email and SMS. Instead of the NotificationService creating these dependencies itself, we’ll inject them from the outside.

Traditional Approach

In a traditional approach, the NotificationService would create and manage its dependencies directly:

public class EmailService {
    public void sendEmail(String message) {
        // logic to send email
        System.out.println("Email sent: " + message);
    }
}

public class SMSService {
    public void sendSMS(String message) {
        // logic to send SMS
        System.out.println("SMS sent: " + message);
    }
}

public class NotificationService {
    private EmailService emailService;
    private SMSService smsService;

    public NotificationService() {
        this.emailService = new EmailService();
        this.smsService = new SMSService();
    }

    public void sendNotification(String message) {
        emailService.sendEmail(message);
        smsService.sendSMS(message);
    }
}

IoC with Dependency Injection

Using Dependency Injection, we’ll inject EmailService and SMSService into NotificationService. We’ll also use a simple dependency injection framework like Spring for illustration.

Step 1: Define Interfaces

First, we define interfaces for the services to decouple the implementations from the NotificationService:

public interface EmailService {
    void sendEmail(String message);
}

public interface SMSService {
    void sendSMS(String message);
}

Step 2: Implement the Interfaces

Next, we implement these interfaces:

public class EmailServiceImpl implements EmailService {
    @Override
    public void sendEmail(String message) {
        // logic to send email
        System.out.println("Email sent: " + message);
    }
}

public class SMSServiceImpl implements SMSService {
    @Override
    public void sendSMS(String message) {
        // logic to send SMS
        System.out.println("SMS sent: " + message);
    }
}

Step 3: Create NotificationService with DI

Now, we create the NotificationService that uses dependency injection:

public class NotificationService {
    private EmailService emailService;
    private SMSService smsService;

    public NotificationService(EmailService emailService, SMSService smsService) {
        this.emailService = emailService;
        this.smsService = smsService;
    }

    public void sendNotification(String message) {
        emailService.sendEmail(message);
        smsService.sendSMS(message);
    }
}

Step 4: Configure Dependencies

Using Spring Framework to wire everything together, we define the beans in a configuration class:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public EmailService emailService() {
        return new EmailServiceImpl();
    }

    @Bean
    public SMSService smsService() {
        return new SMSServiceImpl();
    }

    @Bean
    public NotificationService notificationService() {
        return new NotificationService(emailService(), smsService());
    }
}



Step 5: Use the ApplicationContext to Get Beans

Finally, we use the Spring application context to get the NotificationService bean and send a notification:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        NotificationService notificationService = context.getBean(NotificationService.class);
        notificationService.sendNotification("Hello, this is a test notification!");
    }
}

Summary

By using IoC and Dependency Injection, the NotificationService no longer creates its own dependencies. Instead, these dependencies are provided by an external configuration, making the code more flexible, easier to test, and maintainable. This approach leverages the power of frameworks like Spring to manage dependencies and control the flow of the application.

Leave a Comment