🧩 Dependency Injection & IoC: A Fun Dive into Composition Over Inheritance 📦

firattamur
4 min readJul 11, 2024

--

Photo by Ryoji Iwata on Unsplash

Today we’re diving into some advanced concepts in software development — Dependency Injection (DI), Inversion of Control (IoC), and Composition Over Inheritance. These principles help us write code that’s easier to maintain, test, and extend. Let’s break it down!

Understanding Dependency Injection (DI):

Dependency Injection is a design pattern where an object gets its dependencies from the outside rather than creating them itself. Imagine you’re building a robot 🤖, but instead of making every part yourself, you get pre-made parts and assemble them. 🧩

Types of Dependency Injection

Constructor Injection: Dependencies are provided through the class constructor.

public class Service {
private final Repository repository;

public Service(Repository repository) {
this.repository = repository;
}
}

The robot’s constructor takes in a repository part as an argument. It’s like building a robot with a pre-made arm.

Setter Injection: Dependencies are provided through setter methods.

public class Service {
private Repository repository;

public void setRepository(Repository repository) {
this.repository = repository;
}
}

We can set the robot’s arm after we’ve built the main body. Flexible, right?

Interface Injection: Dependencies are provided through an interface that the client implements.

public interface RepositoryInjector {
void injectRepository(Repository repository);
}

public class Service implements RepositoryInjector {
private Repository repository;

public void injectRepository(Repository repository) {
this.repository = repository;
}
}

Now the robot can implement an interface to get its parts. It’s like giving the robot a plug-and-play system.

Exploring Inversion of Control (IoC)

Inversion of Control flips the script on who controls what. Instead of the robot deciding which parts it needs, an external controller (like a factory) manages it. The robot just follows orders.

IoC Containers

Photo by Guillaume Bolduc on Unsplash

IoC containers manage the lifecycle of objects, resolve dependencies, and inject them automatically. Popular IoC containers include Spring (Java), Guice (Java), and Autofac (C#). Think of the IoC container as a giant factory. It builds and assembles all the robots for you, ensuring they have the right parts.

Benefits of IoC

  • Centralized Configuration: Centralizes dependency management.
  • Flexibility: Allows changing implementations without modifying dependent classes.
  • Scalability: Facilitates building large-scale applications.

Composition Over Inheritance

Composition over inheritance is a principle that suggests using composition (assembling objects) rather than inheritance (extending classes) to achieve code reuse and polymorphism. Instead of making a robot inherit all its abilities from a parent robot, we just give it the parts it needs. More flexible, less baggage.

Benefits of Composition Over Inheritance:

  • ⚙️ Flexibility: Allows changing behavior at runtime.
  • đź”— Reduced Coupling: Decreases tight coupling between classes.
  • ♻️ Reusability: Enables reusing components across different parts of the application.
  • 🛠️ Easier Maintenance: Simplifies maintenance as changes in one component do not impact others.

Dependency Injection and Inversion of Control encourage using composition over inheritance by promoting decoupled and flexible code design.

Example: Notification Service

Without Composition and DI:

public class NotificationService {
public void sendEmail() {
// Send email
}

public void sendSMS() {
// Send SMS
}

public void sendPush() {
// Send push notification
}
}

This is like a robot with hardcoded functions. Want to add a new function? Tough luck, gotta open it up and tinker with the insides.

// Notification interface
public interface Notification {
void send();
}

// Email notification
@Component
public class EmailNotification implements Notification {
public void send() {
// Send email
}
}

// SMS notification
@Component
public class SMSNotification implements Notification {
public void send() {
// Send SMS
}
}

// Push notification
@Component
public class PushNotification implements Notification {
public void send() {
// Send push notification
}
}

// Notification service with DI
@Component
public class NotificationService {
private final List<Notification> notifications;

@Autowired
public NotificationService(List<Notification> notifications) {
this.notifications = notifications;
}

public void sendAll() {
for (Notification notification : notifications) {
notification.send();
}
}
}

Now we’ve got a flexible system. The notification service doesn’t care what types of notifications it sends. Just give it the parts, and it’s good to go.

Conclusion

Dependency Injection, Inversion of Control, and composition over inheritance are powerful principles and patterns that work together to create flexible, maintainable, and modular software systems. By leveraging these techniques, developers can build systems that are easier to understand, test, and extend, while reducing coupling and improving code reusability.

Remember — don’t hardcode your robot’s parts. Use DI and IoC to make it flexible and maintainable. And always prefer composition over inheritance. Your future self will thank you.

--

--

firattamur
firattamur

Written by firattamur

Hey there, I'm firattamur! I have a passion for learning and explaining things.

No responses yet