Summary
Principle Name | Dependency Inversion Principle |
Acronym | DIP |
Principle Type | SOLID |
Tagline | High-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")
Pythonpackage 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");
TypeScriptImplementation 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");
}
}
Javafrom 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")
Pythonpackage 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");
TypeScriptExample #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)
Pythonpackage 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);
TypeScriptImplementation 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);
}
}
Javafrom 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)
Pythonpackage 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