DEV Community

Cover image for Software Design Principle: Inversion of Control(IOC) and Dependency Injection
Ridwan Abiola
Ridwan Abiola

Posted on

Software Design Principle: Inversion of Control(IOC) and Dependency Injection

Software engineering is an established discipline within the broader field of Computer Science. It is a discipline that has made great strides and impressive breakthroughs. Strides relating to powering spacecraft, advances in computer hardware, creating billion-dollar products and platforms, and more recently breakthroughs in AI.

It has also granted individuals skilled in the software-making craft a means to taste freedom from remote work, make a global impact, experience great career opportunities, and more.

Coming into software, some things we hear a lot are the sayings: "Do not reinvent the wheel" and "Standing on the shoulders of giants". These sayings aren't mere statements, they are enduring principles that have stood the test of time.

Software engineering has numerous principles that help make your code maintainable and easy to test. Some popular ones include Separation of Concerns, KISS, SOLID, and Design Patterns. There are also less popular ones and controversial ones as well. Some are now being regarded as bad practices. Though there are a lot, the rest of this article will zoom in on the Inversion of Control, one of the popular ones.

Inversion of Control

Inversion of Control is a software engineering principle that discourages a Developer from creating the object and dependencies his program needs to run directly. Rather he outsources it to a framework or an external entity. Inversion of control (IoC) is where control is inverted, rather than the programmer controls the flow of the program, the framework takes control instead.

In procedural programming, a program's custom code calls reusable libraries to take care of generic tasks, but with inversion of control, it is a framework that calls the custom code. The principle is popular with Java's Spring framework and Javascript's Angular.

Dependency Injection

Dependency Injection is related to the Inversion of Control in the sense that it is the pattern through which Inversion of Control is achieved. In DI, instead of a Class creating its dependencies, the dependencies are provided to the class from the outside. This helps to decouple classes and make them more testable, maintainable, and flexible.

There are three common types of dependency injection:

1.Constructor Injection: Dependencies are provided to a class through its constructor. It is the most common form of DI and ensures a Class has all its dependencies when created.

public class MyClass {
    private final MyDependency dependency;

    public MyClass(MyDependency dependency) {
        this.dependency = dependency;
    }

    // Class methods using the dependency
}
Enter fullscreen mode Exit fullscreen mode

2.Setter Injection: Dependencies are provided to a class through setter methods. This allows for more flexibility, as dependencies can be changed after the class is created.

public class MyClass {
    private MyDependency dependency;

    public void setDependency(MyDependency dependency) {
        this.dependency = dependency;
    }

    // Class methods using the dependency
}
Enter fullscreen mode Exit fullscreen mode

3.Method Injection: Dependencies are provided to individual methods when called. This is less common than constructor or setter injection and is often used in specific cases.

public class MyClass {
    public void doSomething(MyDependency dependency) {
        // Method logic using the dependency
    }
}
Enter fullscreen mode Exit fullscreen mode

This is also the case in Javascript's Angular, DI is a core concept used to provide services, components, and other objects to classes that need them. Angular's DI system is built into the framework and is used extensively throughout Angular applications. Below is a simple example to demonstrate how dependency injection works in Angular:

First, we create a service that we'd like to inject into a component:

// my.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class MyService {
  getMessage(): string {
    return 'Hello from MyService!';
  }
}
Enter fullscreen mode Exit fullscreen mode

Next, we create a component that will consume the service:

// my.component.ts
import { Component } from '@angular/core';
import { MyService } from './my.service';

@Component({
  selector: 'app-my',
  template: `
    <h1>{{ message }}</h1>
  `,
})
export class MyComponent {
  message: string;

  constructor(private myService: MyService) {
    this.message = this.myService.getMessage();
  }
}
Enter fullscreen mode Exit fullscreen mode

In the above code, the MyComponent class has a constructor parameter of type MyService. Angular's DI system will automatically inject an instance of MyService into the MyComponent class when created.

Finally, we add MyService and MyComponent to the list of providers in the Angular module:

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { MyComponent } from './my.component';
import { MyService } from './my.service';

@NgModule({
  declarations: [MyComponent],
  imports: [BrowserModule],
  providers: [MyService],
  bootstrap: [MyComponent],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

In this example, MyService is provided at the root level, meaning a single instance of MyService is shared across the entire application. This allows flexibility in making future changes as the components are loosely coupled.

Some Advantages

  1. There is loose coupling between components
  2. You can easily change dependencies without compromising on quality.
  3. You can leverage code usability and achieve System stability.
  4. Minimal code to maintain.
  5. You can easily replace components without affecting others.

Conclusion

In conclusion, we discussed SE principles and why they are necessary. We focused on IoC and DI, a design pattern through which IoC is achieved. We also mentioned the different types of DI using code samples where appropriate. We mentioned some advantages of IoC and DI. Finally, DI is a powerful design pattern that makes managing complex dependencies in large applications easier.

Thanks for reading, you can follow me on LinkedIn

Top comments (0)