Design Principle: Dependency Inversion Principle(DIP)

Summary

Principle NameDependency Inversion Principle
AcronymDIP
Principle Type SOLID
TaglineHigh-level modules should not depend on low-level modules.
Both should depend on abstractions.
Implementing
Design Patterns
Abstract Factory Pattern
Adapter Pattern
Bridge Pattern
Command Pattern
Dependency Injection
Factory Method Pattern
Strategy Pattern
Related Principles Single Responsibility Principle(SRP)
Open/Closed Principle(OCP)
Liskov Substitution Principle(LSP)
Interface Segregation Principle(ISP)

Definition

Dependency Inversion Principle(DIP) is the fifth and last principle of the SOLID Principles.

This principle is about dependency management. If an object creates and manages its own dependency then it creates a tight coupling.

Instead, we should inject the dependency through a constructor or setter injection. This way DIP promotes a decoupled and flexible architecture.

This principle says-

High-level modules should not depend directly on low-level modules. Instead, both should depend on abstractions (abstract class or interface).

Abstractions should not depend on details, but details should depend on abstractions.

High-level modules: high-level modules are those that contain the complex implementation logic.

Low-level modules: low-level modules are those modules that deal with the specific implementation.

Dependency inversion refers to flipping the dependency relationship, and making both high and low level modules depend on abstruction (not on each other directly)-

  • High-level modules do not depend on low-level module, instead depend on abstraction(usually interface or abstract class).
  • Low-level modules depend on abstruction and implement those interfaces.

Here the abstraction works as a contract between the high-level and low-level modules.

Using this principle we decouple the high-level modules/components from the details of the low-level modules/components. This reduction of coupling helps us to write more extendable and maintainable code.

As the high-level modules remain isolated by depending on the interfaces, from the low-level modules, which enhances the separation of concern.

Follow the steps below to follow this principle –

  • Separate the implementation and abstraction(interface or abstract class).
  • Program to interface.
  • Use the dependency injection pattern to manage the dependencies.

Implementation

Let’s take a look at the examples, where we are using the Dependency Inversion Principle(DIP).

Example #1: Notification Sending

First, consider we want to send some user notifications by email (and maybe some other method).

Initial Implementation (Without DIP)

Here we are creating the email object inside the Notification class, which leads to tight coupling.

// Low-level module/component
class Email {
    public void sendEmail(String msg) {
        // Code for sending email
        System.out.println("Sending email with message: " + msg);
    }
}

// High-level module/component
class Notification {
    private Email email;

    public Notification() {
        // Create an Email object
        this.email = new Email();
    }

    public void send(String msg) {
        // Use the Email object to send email
        email.sendEmail(msg);
    }

    public static void main(String[] args) {
        Notification notification = new Notification();
        notification.send("Test message");
    }
}
Java
# Low-level module/component
# with implementation details
class Email:
    def send_email(self, msg: str) -> None:
        # Code for sending email

        print(f"Sending email with message: {msg}")


# High-level module/component
class Notification:
    def __init__(self) -> None:
        # Create an Email object
        self.email = Email()

    def send(self, msg: str) -> None:
        # Use  the Email object to send email
        self.email.send_email(msg)


# Demo usage
if __name__ == "__main__":
    notification = Notification()
    notification.send("Test message")
Python
package main

import "fmt"

// Low-level module/component
type Email struct{}

func (e Email) SendEmail(msg string) {
    // Code for sending email
    fmt.Println("Sending email with message:", msg)
}

// High-level module/component
type Notification struct {
    email Email
}

func NewNotification() *Notification {
    return &Notification{
        email: Email{},
    }
}

func (n Notification) Send(msg string) {
    // Use the Email object to send email
    n.email.SendEmail(msg)
}

func main() {
    notification := NewNotification()
    notification.Send("Test message")
}
Go
<?php

// Low-level module/component
class Email {
    public function sendEmail(string $msg): void {
        // Code for sending email
        echo "Sending email with message: $msg\n";
    }
}

// High-level module/component
class Notification {
    private $email;

    public function __construct() {
        // Create an Email object
        $this->email = new Email();
    }

    public function send(string $msg): void {
        // Use the Email object to send email
        $this->email->sendEmail($msg);
    }
}

// Demo usage
$notification = new Notification();
$notification->send("Test message");
PHP
// Low-level module/component
class Email {
    sendEmail(msg: string): void {
        // Code for sending email
        console.log(`Sending email with message: ${msg}`);
    }
}

// High-level module/component
class Notification {
    private email: Email;

    constructor() {
        // Create an Email object
        this.email = new Email();
    }

    send(msg: string): void {
        // Use the Email object to send email
        this.email.sendEmail(msg);
    }
}

// Demo usage
const notification = new Notification();
notification.send("Test message");
TypeScript

Implementation with DIP

To make it more robust and maintainable, we have created a Notification interface, and implemented the interface for class Emal, SMS, etc.

Later we created a Notification sender class, where we accept an object of type notification(Email, SMS, etc. class object), and use the object to perform the required operation.

// Abstraction (Interface) for Notification
interface Notification {
    void send(String msg);
}

// Low-level module/component
class Email implements Notification {
    public void send(String msg) {
        // Email sending logic
        System.out.println("Sending email with message: " + msg);
    }
}

// Low-level module/component
class SMS implements Notification {
    public void send(String msg) {
        // SMS sending logic
        System.out.println("Sending SMS with message: " + msg);
    }
}

// High-level module/component
class NotificationSender {
    private Notification notification;

    public NotificationSender(Notification notification) {
        this.notification = notification;
    }

    public void send(String msg) {
        this.notification.send(msg);
    }

    public static void main(String[] args) {
        // Sending an email
        Notification email = new Email();
        NotificationSender emailSender = new NotificationSender(email);
        emailSender.send("Sample email message");

        // Sending an SMS
        Notification sms = new SMS();
        NotificationSender smsSender = new NotificationSender(sms);
        smsSender.send("Sample SMS Text");
    }
}
Java
from abc import ABC, abstractmethod


# Abstraction
# for notification sender
class Notification(ABC):
    @abstractmethod
    def send(self, msg: str) -> None:
        pass


# Low-level module/component
class Email(Notification):
    def send(self, msg: str) -> None:
        # Email sending logic
        print(f"Sending email with message: {msg}")


# Low-level module/component
class SMS(Notification):
    def send(self, msg: str) -> None:
        # Email sending logic
        print(f"Sending SMS with message: {msg}")


# High-level module/component
class NotificationSender:
    def __init__(self, notification: Notification) -> None:
        self.notification = notification

    def send(self, msg: str) -> None:
        self.notification.send(msg)


# Demo usage
if __name__ == "__main__":
    email = Email()
    email_sender = NotificationSender(email)
    email_sender.send("Sample email message")

    sms_sender = NotificationSender(SMS())
    sms_sender.send("Sample SMS Text")
Python
package main

import "fmt"

// Abstraction (interface) for Notification
type Notification interface {
    Send(msg string)
}

// Low-level module/component
type Email struct{}

func (e Email) Send(msg string) {
    // Email sending logic
    fmt.Println("Sending email with message:", msg)
}

// Low-level module/component
type SMS struct{}

func (s SMS) Send(msg string) {
    // SMS sending logic
    fmt.Println("Sending SMS with message:", msg)
}

// High-level module/component
type NotificationSender struct {
    notification Notification
}

func NewNotificationSender(n Notification) *NotificationSender {
    return &NotificationSender{
        notification: n,
    }
}

func (ns NotificationSender) Send(msg string) {
    ns.notification.Send(msg)
}

func main() {
    // Sending an email
    email := Email{}
    emailSender := NewNotificationSender(email)
    emailSender.Send("Sample email message")

    // Sending an SMS
    sms := SMS{}
    smsSender := NewNotificationSender(sms)
    smsSender.Send("Sample SMS Text")
}
Go
<?php

// Abstraction (interface) for Notification
interface Notification {
    public function send(string $msg): void;
}

// Low-level module/component
class Email implements Notification {
    public function send(string $msg): void {
        // Email sending logic
        echo "Sending email with message: $msg\n";
    }
}

// Low-level module/component
class SMS implements Notification {
    public function send(string $msg): void {
        // SMS sending logic
        echo "Sending SMS with message: $msg\n";
    }
}

// High-level module/component
class NotificationSender {
    private $notification;

    public function __construct(Notification $notification) {
        $this->notification = $notification;
    }

    public function send(string $msg): void {
        $this->notification->send($msg);
    }
}

// Demo usage
$emailSender = new NotificationSender(new Email());
$emailSender->send("Sample email message");

$smsSender = new NotificationSender(new SMS());
$smsSender->send("Sample SMS Text");
PHP
// Abstraction (interface) for Notification
interface Notification {
    send(msg: string): void;
}

// Low-level module/component
class Email implements Notification {
    send(msg: string): void {
        // Email sending logic
        console.log(`Sending email with message: ${msg}`);
    }
}

// Low-level module/component
class SMS implements Notification {
    send(msg: string): void {
        // SMS sending logic
        console.log(`Sending SMS with message: ${msg}`);
    }
}

// High-level module/component
class NotificationSender {
    private notification: Notification;

    constructor(notification: Notification) {
        this.notification = notification;
    }

    send(msg: string): void {
        this.notification.send(msg);
    }
}

// Demo usage
const emailSender = new NotificationSender(new Email());
emailSender.send("Sample email message");

const smsSender = new NotificationSender(new SMS());
smsSender.send("Sample SMS Text");
TypeScript

Example #2: Payment Processing

In this example we are considering Payment processing example-

Initial Implementation (Without DIP)

Here we are creating the stripe object inside the Payment class, which leads to tight coupling.

// Low-level module/component
class CreditCardPayment {
    public void processPayment(double amount) {
        // Code for processing credit card payment
        System.out.println("Processing credit card payment of $" + amount);
    }
}

// High-level module/component
class PaymentProcessor {
    private CreditCardPayment paymentMethod;

    public PaymentProcessor() {
        // Create a CreditCardPayment object
        this.paymentMethod = new CreditCardPayment();
    }

    public void process(double amount) {
        // Use the CreditCardPayment object to process payment
        paymentMethod.processPayment(amount);
    }

    public static void main(String[] args) {
        PaymentProcessor processor = new PaymentProcessor();
        processor.process(100.0);
    }
}
Java
# Low-level module/component
# with implementation details
class CreditCardPayment:
    def process_payment(self, amount: float) -> None:
        # Code for processing credit card payment
        print(f"Processing credit card payment of ${amount}")


# High-level module/component
class PaymentProcessor:
    def __init__(self) -> None:
        # Create a CreditCardPayment object
        self.payment_method = CreditCardPayment()

    def process(self, amount: float) -> None:
        # Use the CreditCardPayment object to process payment
        self.payment_method.process_payment(amount)


# Demo usage
if __name__ == "__main__":
    processor = PaymentProcessor()
    processor.process(100.0)
Python
package main

import "fmt"

// Low-level module/component
type CreditCardPayment struct{}

func (c CreditCardPayment) ProcessPayment(amount float64) {
    // Code for processing credit card payment
    fmt.Printf("Processing credit card payment of $%.2f\n", amount)
}

// High-level module/component
type PaymentProcessor struct {
    paymentMethod CreditCardPayment
}

func NewPaymentProcessor() *PaymentProcessor {
    return &PaymentProcessor{
        paymentMethod: CreditCardPayment{},
    }
}

func (p PaymentProcessor) Process(amount float64) {
    // Use the CreditCardPayment object to process payment
    p.paymentMethod.ProcessPayment(amount)
}

func main() {
    processor := NewPaymentProcessor()
    processor.Process(100.0)
}
Go
<?php

// Low-level module/component
class CreditCardPayment {
    public function processPayment(float $amount): void {
        // Code for processing credit card payment
        echo "Processing credit card payment of $$amount\n";
    }
}

// High-level module/component
class PaymentProcessor {
    private $paymentMethod;

    public function __construct() {
        // Create a CreditCardPayment object
        $this->paymentMethod = new CreditCardPayment();
    }

    public function process(float $amount): void {
        // Use the CreditCardPayment object to process payment
        $this->paymentMethod->processPayment($amount);
    }
}

// Demo usage
$processor = new PaymentProcessor();
$processor->process(100.0);
PHP
// Low-level module/component
class CreditCardPayment {
    processPayment(amount: number): void {
        // Code for processing credit card payment
        console.log(`Processing credit card payment of $${amount}`);
    }
}

// High-level module/component
class PaymentProcessor {
    private paymentMethod: CreditCardPayment;

    constructor() {
        // Create a CreditCardPayment object
        this.paymentMethod = new CreditCardPayment();
    }

    process(amount: number): void {
        // Use the CreditCardPayment object to process payment
        this.paymentMethod.processPayment(amount);
    }
}

// Demo usage
const processor = new PaymentProcessor();
processor.process(100.0);
TypeScript

Implementation with DIP

To implement DIP we have created interface Payment and created payment classes that implement the interface.

// Abstraction using Interface
interface PaymentMethod {
    void pay(double amount);
}

// Low-level module/component
class CreditCardPayment implements PaymentMethod {
    public void pay(double amount) {
        // Credit card payment logic
        System.out.println("Processing credit card payment of $" + amount);
    }
}

// Low-level module/component
class PayPalPayment implements PaymentMethod {
    public void pay(double amount) {
        // PayPal payment logic
        System.out.println("Processing PayPal payment of $" + amount);
    }
}

// Low-level module/component
class StripePayment implements PaymentMethod {
    public void pay(double amount) {
        // Stripe payment logic
        System.out.println("Processing Stripe payment of $" + amount);
    }
}

// High-level module/component
class PaymentProcessor {
    private PaymentMethod paymentMethod;

    public PaymentProcessor(PaymentMethod paymentMethod) {
        this.paymentMethod = paymentMethod;
    }

    public void process(double amount) {
        paymentMethod.pay(amount);
    }

    public static void main(String[] args) {
        // Process payment using CreditCard
        PaymentProcessor creditCardProcessor = new PaymentProcessor(new CreditCardPayment());
        creditCardProcessor.process(100.0);

        // Process payment using PayPal
        PaymentProcessor paypalProcessor = new PaymentProcessor(new PayPalPayment());
        paypalProcessor.process(150.0);

        // Process payment using Stripe
        PaymentProcessor stripeProcessor = new PaymentProcessor(new StripePayment());
        stripeProcessor.process(200.0);
    }
}
Java
from typing import Protocol


# Abstraction using Protocol
# for payment method
class PaymentMethod(Protocol):
    def pay(self, amount: float) -> None:
        ...


# Low-level module/component
class CreditCardPayment:
    def pay(self, amount: float) -> None:
        # Credit card payment logic
        print(f"Processing credit card payment of ${amount}")


# Low-level module/component
class PayPalPayment:
    def pay(self, amount: float) -> None:
        # PayPal payment logic
        print(f"Processing PayPal payment of ${amount}")


# Low-level module/component
class StripePayment:
    def pay(self, amount: float) -> None:
        # Stripe payment logic
        print(f"Processing Stripe payment of ${amount}")


# High-level module/component
class PaymentProcessor:
    def __init__(self, payment_method: PaymentMethod) -> None:
        self.payment_method = payment_method

    def process(self, amount: float) -> None:
        self.payment_method.pay(amount)


# Demo usage
if __name__ == "__main__":
    # Process payment using CreditCard
    credit_card = CreditCardPayment()
    processor = PaymentProcessor(credit_card)
    processor.process(100.0)

    # Process payment using PayPal
    paypal = PayPalPayment()
    processor = PaymentProcessor(paypal)
    processor.process(150.0)

    # Process payment using Stripe
    stripe = StripePayment()
    processor = PaymentProcessor(stripe)
    processor.process(200.0)
Python
package main

import "fmt"

// Abstraction using Interface
type PaymentMethod interface {
    Pay(amount float64)
}

// Low-level module/component
type CreditCardPayment struct{}

func (c CreditCardPayment) Pay(amount float64) {
    // Credit card payment logic
    fmt.Printf("Processing credit card payment of $%.2f\n", amount)
}

// Low-level module/component
type PayPalPayment struct{}

func (p PayPalPayment) Pay(amount float64) {
    // PayPal payment logic
    fmt.Printf("Processing PayPal payment of $%.2f\n", amount)
}

// Low-level module/component
type StripePayment struct{}

func (s StripePayment) Pay(amount float64) {
    // Stripe payment logic
    fmt.Printf("Processing Stripe payment of $%.2f\n", amount)
}

// High-level module/component
type PaymentProcessor struct {
    paymentMethod PaymentMethod
}

func NewPaymentProcessor(method PaymentMethod) *PaymentProcessor {
    return &PaymentProcessor{paymentMethod: method}
}

func (p PaymentProcessor) Process(amount float64) {
    p.paymentMethod.Pay(amount)
}

func main() {
    // Process payment using CreditCard
    processor := NewPaymentProcessor(CreditCardPayment{})
    processor.Process(100.0)

    // Process payment using PayPal
    processor = NewPaymentProcessor(PayPalPayment{})
    processor.Process(150.0)

    // Process payment using Stripe
    processor = NewPaymentProcessor(StripePayment{})
    processor.Process(200.0)
}
Go
<?php

// Abstraction using Interface
interface PaymentMethod {
    public function pay(float $amount): void;
}

// Low-level module/component
class CreditCardPayment implements PaymentMethod {
    public function pay(float $amount): void {
        // Credit card payment logic
        echo "Processing credit card payment of $$amount\n";
    }
}

// Low-level module/component
class PayPalPayment implements PaymentMethod {
    public function pay(float $amount): void {
        // PayPal payment logic
        echo "Processing PayPal payment of $$amount\n";
    }
}

// Low-level module/component
class StripePayment implements PaymentMethod {
    public function pay(float $amount): void {
        // Stripe payment logic
        echo "Processing Stripe payment of $$amount\n";
    }
}

// High-level module/component
class PaymentProcessor {
    private $paymentMethod;

    public function __construct(PaymentMethod $paymentMethod) {
        $this->paymentMethod = $paymentMethod;
    }

    public function process(float $amount): void {
        $this->paymentMethod->pay($amount);
    }
}

// Demo usage
$processor = new PaymentProcessor(new CreditCardPayment());
$processor->process(100.0);

$processor = new PaymentProcessor(new PayPalPayment());
$processor->process(150.0);

$processor = new PaymentProcessor(new StripePayment());
$processor->process(200.0);
PHP
// Abstraction using Interface
interface PaymentMethod {
    pay(amount: number): void;
}

// Low-level module/component
class CreditCardPayment implements PaymentMethod {
    pay(amount: number): void {
        // Credit card payment logic
        console.log(`Processing credit card payment of $${amount}`);
    }
}

// Low-level module/component
class PayPalPayment implements PaymentMethod {
    pay(amount: number): void {
        // PayPal payment logic
        console.log(`Processing PayPal payment of $${amount}`);
    }
}

// Low-level module/component
class StripePayment implements PaymentMethod {
    pay(amount: number): void {
        // Stripe payment logic
        console.log(`Processing Stripe payment of $${amount}`);
    }
}

// High-level module/component
class PaymentProcessor {
    private paymentMethod: PaymentMethod;

    constructor(paymentMethod: PaymentMethod) {
        this.paymentMethod = paymentMethod;
    }

    process(amount: number): void {
        this.paymentMethod.pay(amount);
    }
}

// Demo usage
const creditCardProcessor = new PaymentProcessor(new CreditCardPayment());
creditCardProcessor.process(100.0);

const paypalProcessor = new PaymentProcessor(new PayPalPayment());
paypalProcessor.process(150.0);

const stripeProcessor = new PaymentProcessor(new StripePayment());
stripeProcessor.process(200.0);
TypeScript

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.