Dependency Inversion Principle
Dependency Inversion Principle
Section titled “Dependency Inversion Principle”The Dependency Inversion Principle (also known as DIP or inversion principle) is one of the five SOLID principles of object-oriented design. The Dependency Inversion Principle states that:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
This principle helps create flexible, maintainable systems by inverting the traditional dependency flow. Understanding the Dependency Inversion Principle (also referred to as the inversion principle) is essential for building scalable software architectures.
Understanding the Principle
Section titled “Understanding the Principle”The Dependency Inversion Principle ensures that:
- High-level modules (business logic) don’t depend on low-level modules (implementation details)
- Both depend on abstractions (interfaces)
- Details depend on abstractions, not the other way around
- Flexibility - Easy to swap implementations
- Testability - Easy to mock dependencies
In simple terms: Depend on what something can do (interface), not how it does it (implementation)!
Example 1: Light Switch Problem
Section titled “Example 1: Light Switch Problem”Consider a simple light switch system. Let’s see how direct dependencies create problems.
Violating DIP (Bad Approach)
Section titled “Violating DIP (Bad Approach)”class LightBulb: """Low-level module - concrete implementation""" def turn_on(self): print("Light bulb turned on")
def turn_off(self): print("Light bulb turned off")
class LightSwitch: """❌ High-level module - depends on concrete LightBulb""" def __init__(self): self.bulb = LightBulb() # ❌ Direct dependency on concrete class!
def toggle(self): if self.bulb.is_on: # Assuming we track state self.bulb.turn_off() else: self.bulb.turn_on()
# Problem: What if we want to use LED instead of LightBulb?class LED: """New type of light""" def turn_on(self): print("LED turned on")
def turn_off(self): print("LED turned off")
# ❌ Can't use LED with LightSwitch without modifying LightSwitch!# LightSwitch is tightly coupled to LightBulb// Low-level module - concrete implementationpublic class LightBulb { private boolean isOn = false;
public void turnOn() { isOn = true; System.out.println("Light bulb turned on"); }
public void turnOff() { isOn = false; System.out.println("Light bulb turned off"); }
public boolean isOn() { return isOn; }}
// ❌ High-level module - depends on concrete LightBulbpublic class LightSwitch { private LightBulb bulb; // ❌ Direct dependency on concrete class!
public LightSwitch() { this.bulb = new LightBulb(); }
public void toggle() { if (bulb.isOn()) { bulb.turnOff(); } else { bulb.turnOn(); } }}
// Problem: What if we want to use LED instead of LightBulb?class LED { // New type of light public void turnOn() { System.out.println("LED turned on"); }
public void turnOff() { System.out.println("LED turned off"); }}
// ❌ Can't use LED with LightSwitch without modifying LightSwitch!// LightSwitch is tightly coupled to LightBulb// Low-level module - concrete implementationclass LightBulb { private isOn: boolean = false;
turnOn(): void { this.isOn = true; console.log("Light bulb turned on"); }
turnOff(): void { this.isOn = false; console.log("Light bulb turned off"); }
getIsOn(): boolean { return this.isOn; }}
// ❌ High-level module - depends on concrete LightBulbclass LightSwitch { private bulb: LightBulb; // ❌ Direct dependency on concrete class!
constructor() { this.bulb = new LightBulb(); }
toggle(): void { if (this.bulb.getIsOn()) { this.bulb.turnOff(); } else { this.bulb.turnOn(); } }}
// Problem: What if we want to use LED instead of LightBulb?class LED { // New type of light turnOn(): void { console.log("LED turned on"); }
turnOff(): void { console.log("LED turned off"); }}
// ❌ Can't use LED with LightSwitch without modifying LightSwitch!// LightSwitch is tightly coupled to LightBulb#include <iostream>
// Low-level module - concrete implementationclass LightBulb {private: bool isOn = false;
public: void turnOn() { isOn = true; std::cout << "Light bulb turned on" << std::endl; }
void turnOff() { isOn = false; std::cout << "Light bulb turned off" << std::endl; }
bool getIsOn() const { return isOn; }};
// ❌ High-level module - depends on concrete LightBulbclass LightSwitch {private: LightBulb bulb; // ❌ Direct dependency on concrete class!
public: LightSwitch() { // bulb is created automatically }
void toggle() { if (bulb.getIsOn()) { bulb.turnOff(); } else { bulb.turnOn(); } }};
// Problem: What if we want to use LED instead of LightBulb?class LED {public: // New type of light void turnOn() { std::cout << "LED turned on" << std::endl; }
void turnOff() { std::cout << "LED turned off" << std::endl; }};
// ❌ Can't use LED with LightSwitch without modifying LightSwitch!// LightSwitch is tightly coupled to LightBulbusing System;
// Low-level module - concrete implementationpublic class LightBulb{ private bool isOn = false;
public void TurnOn() { isOn = true; Console.WriteLine("Light bulb turned on"); }
public void TurnOff() { isOn = false; Console.WriteLine("Light bulb turned off"); }
public bool IsOn() { return isOn; }}
// ❌ High-level module - depends on concrete LightBulbpublic class LightSwitch{ private LightBulb bulb; // ❌ Direct dependency on concrete class!
public LightSwitch() { this.bulb = new LightBulb(); }
public void Toggle() { if (bulb.IsOn()) { bulb.TurnOff(); } else { bulb.TurnOn(); } }}
// Problem: What if we want to use LED instead of LightBulb?class LED{ // New type of light public void TurnOn() { Console.WriteLine("LED turned on"); }
public void TurnOff() { Console.WriteLine("LED turned off"); }}
// ❌ Can't use LED with LightSwitch without modifying LightSwitch!// LightSwitch is tightly coupled to LightBulbpackage main
import "fmt"
// Low-level module - concrete implementationtype LightBulb struct{ isOn bool }
func (b *LightBulb) TurnOn() { b.isOn = true; fmt.Println("Light bulb turned on") }func (b *LightBulb) TurnOff() { b.isOn = false; fmt.Println("Light bulb turned off") }func (b *LightBulb) IsOn() bool { return b.isOn }
// ❌ High-level module - depends on concrete LightBulbtype LightSwitch struct { bulb *LightBulb // ❌ Direct dependency on concrete type!}
func NewLightSwitch() *LightSwitch { return &LightSwitch{bulb: &LightBulb{}} // ❌ Creates its own dependency}
func (s *LightSwitch) Toggle() { if s.bulb.IsOn() { s.bulb.TurnOff() } else { s.bulb.TurnOn() }}
// Problem: What if we want to use LED instead?type LED struct{}
func (l *LED) TurnOn() { fmt.Println("LED turned on") }func (l *LED) TurnOff() { fmt.Println("LED turned off") }
// ❌ Can't use LED with LightSwitch without modifying LightSwitch!struct LightBulb;impl LightBulb { fn turn_on(&self) { println!("Light on"); }}struct Switch { bulb: LightBulb,}impl Switch { fn operate(&self) { self.bulb.turn_on(); }}// Switch depends directly on LightBulb.Following DIP (Good Approach)
Section titled “Following DIP (Good Approach)”from abc import ABC, abstractmethod
class Switchable(ABC): """Abstraction - defines what a switchable device can do""" @abstractmethod def turn_on(self): """Turn the device on""" pass
@abstractmethod def turn_off(self): """Turn the device off""" pass
class LightBulb(Switchable): """Low-level module - concrete implementation""" def __init__(self): self.is_on = False
def turn_on(self): self.is_on = True print("Light bulb turned on")
def turn_off(self): self.is_on = False print("Light bulb turned off")
class LED(Switchable): """Low-level module - another concrete implementation""" def __init__(self): self.is_on = False
def turn_on(self): self.is_on = True print("LED turned on")
def turn_off(self): self.is_on = False print("LED turned off")
class LightSwitch: """✓ High-level module - depends on abstraction""" def __init__(self, device: Switchable): # ✓ Depends on abstraction! self.device = device self.is_on = False
def toggle(self): if self.is_on: self.device.turn_off() self.is_on = False else: self.device.turn_on() self.is_on = True
# Usage - Flexible and testable!bulb = LightBulb()switch1 = LightSwitch(bulb) # ✓ Works with LightBulbswitch1.toggle() # Turns on bulb
led = LED()switch2 = LightSwitch(led) # ✓ Works with LED - no code changes!switch2.toggle() # Turns on LED
# Easy to add new devices without modifying LightSwitch!class Fan(Switchable): def turn_on(self): print("Fan turned on")
def turn_off(self): print("Fan turned off")
fan = Fan()switch3 = LightSwitch(fan) # ✓ Works with Fan too!switch3.toggle()// Abstraction - defines what a switchable device can dopublic interface Switchable { // Turn the device on void turnOn();
// Turn the device off void turnOff();}
// Low-level module - concrete implementationpublic class LightBulb implements Switchable { private boolean isOn = false;
public LightBulb() { this.isOn = false; }
@Override public void turnOn() { isOn = true; System.out.println("Light bulb turned on"); }
@Override public void turnOff() { isOn = false; System.out.println("Light bulb turned off"); }}
// Low-level module - another concrete implementationpublic class LED implements Switchable { private boolean isOn = false;
public LED() { this.isOn = false; }
@Override public void turnOn() { isOn = true; System.out.println("LED turned on"); }
@Override public void turnOff() { isOn = false; System.out.println("LED turned off"); }}
// ✓ High-level module - depends on abstractionpublic class LightSwitch { private Switchable device; // ✓ Depends on abstraction! private boolean isOn = false;
public LightSwitch(Switchable device) { this.device = device; }
public void toggle() { if (isOn) { device.turnOff(); isOn = false; } else { device.turnOn(); isOn = true; } }}
// Easy to add new devices without modifying LightSwitch!class Fan implements Switchable { @Override public void turnOn() { System.out.println("Fan turned on"); }
@Override public void turnOff() { System.out.println("Fan turned off"); }}
// Usage - Flexible and testable!public class Main { public static void main(String[] args) { LightBulb bulb = new LightBulb(); LightSwitch switch1 = new LightSwitch(bulb); // ✓ Works with LightBulb switch1.toggle(); // Turns on bulb
LED led = new LED(); LightSwitch switch2 = new LightSwitch(led); // ✓ Works with LED - no code changes! switch2.toggle(); // Turns on LED
Fan fan = new Fan(); LightSwitch switch3 = new LightSwitch(fan); // ✓ Works with Fan too! switch3.toggle(); }}// Abstraction - defines what a switchable device can dointerface Switchable { /** Turn the device on */ turnOn(): void;
/** Turn the device off */ turnOff(): void;}
// Low-level module - concrete implementationclass LightBulb implements Switchable { private isOn: boolean = false;
constructor() { this.isOn = false; }
turnOn(): void { this.isOn = true; console.log("Light bulb turned on"); }
turnOff(): void { this.isOn = false; console.log("Light bulb turned off"); }}
// Low-level module - another concrete implementationclass LED implements Switchable { private isOn: boolean = false;
constructor() { this.isOn = false; }
turnOn(): void { this.isOn = true; console.log("LED turned on"); }
turnOff(): void { this.isOn = false; console.log("LED turned off"); }}
// ✓ High-level module - depends on abstractionclass LightSwitch { private device: Switchable; // ✓ Depends on abstraction! private isOn: boolean = false;
constructor(device: Switchable) { this.device = device; }
toggle(): void { if (this.isOn) { this.device.turnOff(); this.isOn = false; } else { this.device.turnOn(); this.isOn = true; } }}
// Usage - Flexible and testable!const bulb = new LightBulb();const switch1 = new LightSwitch(bulb); // ✓ Works with LightBulbswitch1.toggle(); // Turns on bulb
const led = new LED();const switch2 = new LightSwitch(led); // ✓ Works with LED - no code changes!switch2.toggle(); // Turns on LED
// Easy to add new devices without modifying LightSwitch!class Fan implements Switchable { turnOn(): void { console.log("Fan turned on"); }
turnOff(): void { console.log("Fan turned off"); }}
const fan = new Fan();const switch3 = new LightSwitch(fan); // ✓ Works with Fan too!switch3.toggle();#include <iostream>
// Abstraction - defines what a switchable device can doclass Switchable {public: virtual ~Switchable() = default; // Turn the device on virtual void turnOn() = 0; // Turn the device off virtual void turnOff() = 0;};
// Low-level module - concrete implementationclass LightBulb : public Switchable {private: bool isOn = false;
public: LightBulb() : isOn(false) {}
void turnOn() override { isOn = true; std::cout << "Light bulb turned on" << std::endl; }
void turnOff() override { isOn = false; std::cout << "Light bulb turned off" << std::endl; }};
// Low-level module - another concrete implementationclass LED : public Switchable {private: bool isOn = false;
public: LED() : isOn(false) {}
void turnOn() override { isOn = true; std::cout << "LED turned on" << std::endl; }
void turnOff() override { isOn = false; std::cout << "LED turned off" << std::endl; }};
// ✓ High-level module - depends on abstractionclass LightSwitch {private: Switchable* device; // ✓ Depends on abstraction! bool isOn = false;
public: LightSwitch(Switchable* device) : device(device), isOn(false) {}
void toggle() { if (isOn) { device->turnOff(); isOn = false; } else { device->turnOn(); isOn = true; } }};
// Easy to add new devices without modifying LightSwitch!class Fan : public Switchable {public: void turnOn() override { std::cout << "Fan turned on" << std::endl; }
void turnOff() override { std::cout << "Fan turned off" << std::endl; }};
int main() { // Usage - Flexible and testable! LightBulb bulb; LightSwitch switch1(&bulb); // ✓ Works with LightBulb switch1.toggle(); // Turns on bulb
LED led; LightSwitch switch2(&led); // ✓ Works with LED - no code changes! switch2.toggle(); // Turns on LED
Fan fan; LightSwitch switch3(&fan); // ✓ Works with Fan too! switch3.toggle();
return 0;}using System;
// Abstraction - defines what a switchable device can dopublic interface ISwitchable{ // Turn the device on void TurnOn();
// Turn the device off void TurnOff();}
// Low-level module - concrete implementationpublic class LightBulb : ISwitchable{ private bool isOn = false;
public LightBulb() { this.isOn = false; }
public void TurnOn() { isOn = true; Console.WriteLine("Light bulb turned on"); }
public void TurnOff() { isOn = false; Console.WriteLine("Light bulb turned off"); }}
// Low-level module - another concrete implementationpublic class LED : ISwitchable{ private bool isOn = false;
public LED() { this.isOn = false; }
public void TurnOn() { isOn = true; Console.WriteLine("LED turned on"); }
public void TurnOff() { isOn = false; Console.WriteLine("LED turned off"); }}
// ✓ High-level module - depends on abstractionpublic class LightSwitch{ private ISwitchable device; // ✓ Depends on abstraction! private bool isOn = false;
public LightSwitch(ISwitchable device) { this.device = device; }
public void Toggle() { if (isOn) { device.TurnOff(); isOn = false; } else { device.TurnOn(); isOn = true; } }}
// Easy to add new devices without modifying LightSwitch!class Fan : ISwitchable{ public void TurnOn() { Console.WriteLine("Fan turned on"); }
public void TurnOff() { Console.WriteLine("Fan turned off"); }}
class Program{ static void Main() { // Usage - Flexible and testable! LightBulb bulb = new LightBulb(); LightSwitch switch1 = new LightSwitch(bulb); // ✓ Works with LightBulb switch1.Toggle(); // Turns on bulb
LED led = new LED(); LightSwitch switch2 = new LightSwitch(led); // ✓ Works with LED - no code changes! switch2.Toggle(); // Turns on LED
Fan fan = new Fan(); LightSwitch switch3 = new LightSwitch(fan); // ✓ Works with Fan too! switch3.Toggle(); }}package main
import "fmt"
// Abstraction - defines what a switchable device can dotype Switchable interface { TurnOn() TurnOff()}
// Low-level modules - concrete implementationstype LightBulb struct{ isOn bool }
func (b *LightBulb) TurnOn() { b.isOn = true; fmt.Println("Light bulb turned on") }func (b *LightBulb) TurnOff() { b.isOn = false; fmt.Println("Light bulb turned off") }
type LED struct{ isOn bool }
func (l *LED) TurnOn() { l.isOn = true; fmt.Println("LED turned on") }func (l *LED) TurnOff() { l.isOn = false; fmt.Println("LED turned off") }
type Fan struct{}
func (f *Fan) TurnOn() { fmt.Println("Fan turned on") }func (f *Fan) TurnOff() { fmt.Println("Fan turned off") }
// ✓ High-level module - depends on abstractiontype LightSwitch struct { device Switchable // ✓ Depends on abstraction! isOn bool}
func NewLightSwitch(device Switchable) *LightSwitch { return &LightSwitch{device: device}}
func (s *LightSwitch) Toggle() { if s.isOn { s.device.TurnOff() s.isOn = false } else { s.device.TurnOn() s.isOn = true }}
func main() { switch1 := NewLightSwitch(&LightBulb{}) // ✓ Works with LightBulb switch1.Toggle()
switch2 := NewLightSwitch(&LED{}) // ✓ Works with LED - no code changes! switch2.Toggle()
switch3 := NewLightSwitch(&Fan{}) // ✓ Works with Fan too! switch3.Toggle()}trait Switchable { fn turn_on(&self); fn turn_off(&self);}struct LightBulb;struct Fan;impl Switchable for LightBulb { fn turn_on(&self) { println!("Light on"); } fn turn_off(&self) { println!("Light off"); }}impl Switchable for Fan { fn turn_on(&self) { println!("Fan on"); } fn turn_off(&self) { println!("Fan off"); }}struct Switch<'a> { device: &'a dyn Switchable,}impl<'a> Switch<'a> { fn operate(&self) { self.device.turn_on(); }}Why this follows DIP:
LightSwitchdepends onSwitchableabstraction, not concrete classes- Can easily swap implementations without modifying
LightSwitch - Easy to test - can create mock
Switchableimplementations - New devices can be added without changing existing code
Example 2: Order Processing System
Section titled “Example 2: Order Processing System”Consider an e-commerce order processing system that needs to send notifications and process payments.
Violating DIP (Bad Approach)
Section titled “Violating DIP (Bad Approach)”class EmailService: """Low-level module - concrete email implementation""" def send_email(self, to: str, subject: str, body: str): print(f"Sending email to {to}: {subject}") # Actual email sending logic...
class StripePaymentProcessor: """Low-level module - concrete payment implementation""" def charge(self, amount: float, card_token: str): print(f"Charging ${amount} via Stripe") # Actual Stripe API call...
class OrderProcessor: """❌ High-level module - depends on concrete implementations""" def __init__(self): self.email_service = EmailService() # ❌ Direct dependency! self.payment_processor = StripePaymentProcessor() # ❌ Direct dependency!
def process_order(self, order: dict): """Process an order""" # Process payment self.payment_processor.charge( order['amount'], order['card_token'] )
# Send confirmation email self.email_service.send_email( to=order['email'], subject="Order Confirmation", body=f"Your order for ${order['amount']} has been processed" )// Low-level module - concrete email implementationpublic class EmailService { public void sendEmail(String to, String subject, String body) { System.out.println("Sending email to " + to + ": " + subject); // Actual email sending logic... }}
// Low-level module - concrete payment implementationpublic class StripePaymentProcessor { public void charge(double amount, String cardToken) { System.out.println("Charging $" + amount + " via Stripe"); // Actual Stripe API call... }}
// ❌ High-level module - depends on concrete implementationspublic class OrderProcessor { private EmailService emailService; // ❌ Direct dependency! private StripePaymentProcessor paymentProcessor; // ❌ Direct dependency!
public OrderProcessor() { this.emailService = new EmailService(); this.paymentProcessor = new StripePaymentProcessor(); }
// Process an order public void processOrder(Order order) { // Process payment paymentProcessor.charge(order.getAmount(), order.getCardToken());
// Send confirmation email emailService.sendEmail( order.getEmail(), "Order Confirmation", "Your order for $" + order.getAmount() + " has been processed" ); }}
class Order { private double amount; private String cardToken; private String email;
public Order(double amount, String cardToken, String email) { this.amount = amount; this.cardToken = cardToken; this.email = email; }
public double getAmount() { return amount; } public String getCardToken() { return cardToken; } public String getEmail() { return email; }}// Low-level module - concrete email implementationclass EmailService { sendEmail(to: string, subject: string, body: string): void { console.log(`Sending email to ${to}: ${subject}`); // Actual email sending logic... }}
// Low-level module - concrete payment implementationclass StripePaymentProcessor { charge(amount: number, cardToken: string): void { console.log(`Charging $${amount} via Stripe`); // Actual Stripe API call... }}
// ❌ High-level module - depends on concrete implementationsclass OrderProcessor { private emailService: EmailService; // ❌ Direct dependency! private paymentProcessor: StripePaymentProcessor; // ❌ Direct dependency!
constructor() { this.emailService = new EmailService(); this.paymentProcessor = new StripePaymentProcessor(); }
/** Process an order */ processOrder(order: {amount: number, cardToken: string, email: string}): void { // Process payment this.paymentProcessor.charge( order.amount, order.cardToken );
// Send confirmation email this.emailService.sendEmail( order.email, "Order Confirmation", `Your order for $${order.amount} has been processed` ); }}#include <string>#include <iostream>
// Low-level module - concrete email implementationclass EmailService {public: void sendEmail(const std::string& to, const std::string& subject, const std::string& body) { std::cout << "Sending email to " << to << ": " << subject << std::endl; // Actual email sending logic... }};
// Low-level module - concrete payment implementationclass StripePaymentProcessor {public: void charge(double amount, const std::string& cardToken) { std::cout << "Charging $" << amount << " via Stripe" << std::endl; // Actual Stripe API call... }};
// ❌ High-level module - depends on concrete implementationsclass OrderProcessor {private: EmailService emailService; // ❌ Direct dependency! StripePaymentProcessor paymentProcessor; // ❌ Direct dependency!
public: OrderProcessor() { // Dependencies created internally }
// Process an order void processOrder(double amount, const std::string& cardToken, const std::string& email) { // Process payment paymentProcessor.charge(amount, cardToken);
// Send confirmation email emailService.sendEmail( email, "Order Confirmation", "Your order for $" + std::to_string(amount) + " has been processed" ); }};using System;
// Low-level module - concrete email implementationpublic class EmailService{ public void SendEmail(string to, string subject, string body) { Console.WriteLine($"Sending email to {to}: {subject}"); // Actual email sending logic... }}
// Low-level module - concrete payment implementationpublic class StripePaymentProcessor{ public void Charge(double amount, string cardToken) { Console.WriteLine($"Charging ${amount} via Stripe"); // Actual Stripe API call... }}
// ❌ High-level module - depends on concrete implementationspublic class OrderProcessor{ private EmailService emailService; // ❌ Direct dependency! private StripePaymentProcessor paymentProcessor; // ❌ Direct dependency!
public OrderProcessor() { this.emailService = new EmailService(); this.paymentProcessor = new StripePaymentProcessor(); }
// Process an order public void ProcessOrder(double amount, string cardToken, string email) { // Process payment paymentProcessor.Charge(amount, cardToken);
// Send confirmation email emailService.SendEmail( email, "Order Confirmation", $"Your order for ${amount} has been processed" ); }}package main
import "fmt"
// Low-level module - concrete email implementationtype EmailService struct{}
func (s *EmailService) SendEmail(to, subject, body string) { fmt.Printf("Sending email to %s: %s\n", to, subject)}
// Low-level module - concrete payment implementationtype StripePaymentProcessor struct{}
func (p *StripePaymentProcessor) Charge(amount float64, cardToken string) { fmt.Printf("Charging $%.2f via Stripe\n", amount)}
// ❌ High-level module - depends on concrete implementationstype OrderProcessor struct { emailService *EmailService // ❌ Direct dependency! paymentProcessor *StripePaymentProcessor // ❌ Direct dependency!}
func NewOrderProcessor() *OrderProcessor { return &OrderProcessor{ emailService: &EmailService{}, paymentProcessor: &StripePaymentProcessor{}, }}
func (o *OrderProcessor) ProcessOrder(amount float64, cardToken, email string) { o.paymentProcessor.Charge(amount, cardToken) o.emailService.SendEmail(email, "Order Confirmation", fmt.Sprintf("Your order for $%.2f has been processed", amount))}struct MySqlDatabase;struct EmailSender;impl MySqlDatabase { fn save_order(&self) { println!("Saved order"); }}impl EmailSender { fn send_confirmation(&self) { println!("Email sent"); }}struct OrderService { db: MySqlDatabase, email: EmailSender,}impl OrderService { fn process_order(&self) { self.db.save_order(); self.email.send_confirmation(); }}Why this violates DIP:
OrderProcessordirectly depends on concreteEmailServiceandStripePaymentProcessor- Can’t swap implementations without modifying
OrderProcessor - Hard to test - can’t inject mock dependencies
- Tight coupling reduces flexibility
Following DIP (Good Approach)
Section titled “Following DIP (Good Approach)”from abc import ABC, abstractmethod
# Abstractions - define what services can doclass NotificationService(ABC): """Abstraction for notification services""" @abstractmethod def send_notification(self, to: str, message: str): """Send a notification""" pass
class PaymentProcessor(ABC): """Abstraction for payment processors""" @abstractmethod def process_payment(self, amount: float, details: dict) -> bool: """Process a payment and return success status""" pass
# Low-level modules - concrete implementationsclass EmailService(NotificationService): """Low-level module - email implementation""" def send_notification(self, to: str, message: str): print(f"Sending email to {to}: {message}") # Actual email sending logic...
class SMSService(NotificationService): """Low-level module - SMS implementation""" def send_notification(self, to: str, message: str): print(f"Sending SMS to {to}: {message}") # Actual SMS sending logic...
class StripePaymentProcessor(PaymentProcessor): """Low-level module - Stripe implementation""" def process_payment(self, amount: float, details: dict) -> bool: print(f"Processing ${amount} via Stripe") # Actual Stripe API call... return True
class PayPalPaymentProcessor(PaymentProcessor): """Low-level module - PayPal implementation""" def process_payment(self, amount: float, details: dict) -> bool: print(f"Processing ${amount} via PayPal") # Actual PayPal API call... return True
# High-level module - depends on abstractionsclass OrderProcessor: """✓ High-level module - depends on abstractions""" def __init__( self, notification_service: NotificationService, # ✓ Abstraction! payment_processor: PaymentProcessor # ✓ Abstraction! ): self.notification_service = notification_service self.payment_processor = payment_processor
def process_order(self, order: dict): """Process an order""" # Process payment success = self.payment_processor.process_payment( order['amount'], order['payment_details'] )
if success: # Send confirmation notification self.notification_service.send_notification( to=order['email'], message=f"Your order for ${order['amount']} has been processed" )
# Usage - Flexible and testable!# Can easily swap implementationsemail_service = EmailService()stripe_processor = StripePaymentProcessor()order_processor1 = OrderProcessor(email_service, stripe_processor)
# Switch to SMS and PayPal - no code changes needed!sms_service = SMSService()paypal_processor = PayPalPaymentProcessor()order_processor2 = OrderProcessor(sms_service, paypal_processor)
# Easy to test with mocksclass MockNotificationService(NotificationService): def send_notification(self, to: str, message: str): print(f"[MOCK] Would send to {to}: {message}")
class MockPaymentProcessor(PaymentProcessor): def process_payment(self, amount: float, details: dict) -> bool: print(f"[MOCK] Would process ${amount}") return True
mock_notification = MockNotificationService()mock_payment = MockPaymentProcessor()test_processor = OrderProcessor(mock_notification, mock_payment)# Easy to test without real services!import java.util.Map;
// Abstractions - define what services can dopublic interface NotificationService { // Send a notification void sendNotification(String to, String message);}
public interface PaymentProcessor { // Process a payment and return success status boolean processPayment(double amount, Map<String, String> details);}
// Low-level modules - concrete implementationspublic class EmailService implements NotificationService { // Low-level module - email implementation @Override public void sendNotification(String to, String message) { System.out.println("Sending email to " + to + ": " + message); // Actual email sending logic... }}
public class SMSService implements NotificationService { // Low-level module - SMS implementation @Override public void sendNotification(String to, String message) { System.out.println("Sending SMS to " + to + ": " + message); // Actual SMS sending logic... }}
public class StripePaymentProcessor implements PaymentProcessor { // Low-level module - Stripe implementation @Override public boolean processPayment(double amount, Map<String, String> details) { System.out.println("Processing $" + amount + " via Stripe"); // Actual Stripe API call... return true; }}
public class PayPalPaymentProcessor implements PaymentProcessor { // Low-level module - PayPal implementation @Override public boolean processPayment(double amount, Map<String, String> details) { System.out.println("Processing $" + amount + " via PayPal"); // Actual PayPal API call... return true; }}
// High-level module - depends on abstractionspublic class OrderProcessor { // ✓ High-level module - depends on abstractions private NotificationService notificationService; // ✓ Abstraction! private PaymentProcessor paymentProcessor; // ✓ Abstraction!
public OrderProcessor( NotificationService notificationService, PaymentProcessor paymentProcessor ) { this.notificationService = notificationService; this.paymentProcessor = paymentProcessor; }
// Process an order public void processOrder(Order order) { // Process payment Map<String, String> paymentDetails = order.getPaymentDetails(); boolean success = paymentProcessor.processPayment( order.getAmount(), paymentDetails );
if (success) { // Send confirmation notification notificationService.sendNotification( order.getEmail(), "Your order for $" + order.getAmount() + " has been processed" ); } }}
class Order { private double amount; private Map<String, String> paymentDetails; private String email;
public Order(double amount, Map<String, String> paymentDetails, String email) { this.amount = amount; this.paymentDetails = paymentDetails; this.email = email; }
public double getAmount() { return amount; } public Map<String, String> getPaymentDetails() { return paymentDetails; } public String getEmail() { return email; }}
// Usage - Flexible and testable!public class Main { public static void main(String[] args) { // Can easily swap implementations EmailService emailService = new EmailService(); StripePaymentProcessor stripeProcessor = new StripePaymentProcessor(); OrderProcessor orderProcessor1 = new OrderProcessor(emailService, stripeProcessor);
// Switch to SMS and PayPal - no code changes needed! SMSService smsService = new SMSService(); PayPalPaymentProcessor paypalProcessor = new PayPalPaymentProcessor(); OrderProcessor orderProcessor2 = new OrderProcessor(smsService, paypalProcessor);
// Easy to test with mocks // (Mock classes would implement the interfaces) }}// Abstractions - define what services can dointerface NotificationService { /** Send a notification */ sendNotification(to: string, message: string): void;}
interface PaymentProcessor { /** Process a payment and return success status */ processPayment(amount: number, details: Record<string, any>): boolean;}
// Low-level modules - concrete implementationsclass EmailService implements NotificationService { /** Low-level module - email implementation */ sendNotification(to: string, message: string): void { console.log(`Sending email to ${to}: ${message}`); // Actual email sending logic... }}
class SMSService implements NotificationService { /** Low-level module - SMS implementation */ sendNotification(to: string, message: string): void { console.log(`Sending SMS to ${to}: ${message}`); // Actual SMS sending logic... }}
class StripePaymentProcessor implements PaymentProcessor { /** Low-level module - Stripe implementation */ processPayment(amount: number, details: Record<string, any>): boolean { console.log(`Processing $${amount} via Stripe`); // Actual Stripe API call... return true; }}
class PayPalPaymentProcessor implements PaymentProcessor { /** Low-level module - PayPal implementation */ processPayment(amount: number, details: Record<string, any>): boolean { console.log(`Processing $${amount} via PayPal`); // Actual PayPal API call... return true; }}
// High-level module - depends on abstractionsclass OrderProcessor { /** ✓ High-level module - depends on abstractions */ private notificationService: NotificationService; // ✓ Abstraction! private paymentProcessor: PaymentProcessor; // ✓ Abstraction!
constructor( notificationService: NotificationService, paymentProcessor: PaymentProcessor ) { this.notificationService = notificationService; this.paymentProcessor = paymentProcessor; }
/** Process an order */ processOrder(order: {amount: number, paymentDetails: Record<string, any>, email: string}): void { // Process payment const success = this.paymentProcessor.processPayment( order.amount, order.paymentDetails );
if (success) { // Send confirmation notification this.notificationService.sendNotification( order.email, `Your order for $${order.amount} has been processed` ); } }}
// Usage - Flexible and testable!// Can easily swap implementationsconst emailService = new EmailService();const stripeProcessor = new StripePaymentProcessor();const orderProcessor1 = new OrderProcessor(emailService, stripeProcessor);
// Switch to SMS and PayPal - no code changes needed!const smsService = new SMSService();const paypalProcessor = new PayPalPaymentProcessor();const orderProcessor2 = new OrderProcessor(smsService, paypalProcessor);
// Easy to test with mocksclass MockNotificationService implements NotificationService { sendNotification(to: string, message: string): void { console.log(`[MOCK] Would send to ${to}: ${message}`); }}
class MockPaymentProcessor implements PaymentProcessor { processPayment(amount: number, details: Record<string, any>): boolean { console.log(`[MOCK] Would process $${amount}`); return true; }}
const mockNotification = new MockNotificationService();const mockPayment = new MockPaymentProcessor();const testProcessor = new OrderProcessor(mockNotification, mockPayment);// Easy to test without real services!#include <string>#include <iostream>#include <map>
// Abstractions - define what services can doclass NotificationService {public: virtual ~NotificationService() = default; // Send a notification virtual void sendNotification(const std::string& to, const std::string& message) = 0;};
class PaymentProcessor {public: virtual ~PaymentProcessor() = default; // Process a payment and return success status virtual bool processPayment(double amount, const std::map<std::string, std::string>& details) = 0;};
// Low-level modules - concrete implementationsclass EmailService : public NotificationService {public: // Low-level module - email implementation void sendNotification(const std::string& to, const std::string& message) override { std::cout << "Sending email to " << to << ": " << message << std::endl; // Actual email sending logic... }};
class SMSService : public NotificationService {public: // Low-level module - SMS implementation void sendNotification(const std::string& to, const std::string& message) override { std::cout << "Sending SMS to " << to << ": " << message << std::endl; // Actual SMS sending logic... }};
class StripePaymentProcessor : public PaymentProcessor {public: // Low-level module - Stripe implementation bool processPayment(double amount, const std::map<std::string, std::string>& details) override { std::cout << "Processing $" << amount << " via Stripe" << std::endl; // Actual Stripe API call... return true; }};
class PayPalPaymentProcessor : public PaymentProcessor {public: // Low-level module - PayPal implementation bool processPayment(double amount, const std::map<std::string, std::string>& details) override { std::cout << "Processing $" << amount << " via PayPal" << std::endl; // Actual PayPal API call... return true; }};
// High-level module - depends on abstractionsclass OrderProcessor {private: // ✓ High-level module - depends on abstractions NotificationService* notificationService; // ✓ Abstraction! PaymentProcessor* paymentProcessor; // ✓ Abstraction!
public: OrderProcessor( NotificationService* notificationService, PaymentProcessor* paymentProcessor ) : notificationService(notificationService), paymentProcessor(paymentProcessor) {}
// Process an order void processOrder(double amount, const std::map<std::string, std::string>& paymentDetails, const std::string& email) { // Process payment bool success = paymentProcessor->processPayment(amount, paymentDetails);
if (success) { // Send confirmation notification notificationService->sendNotification( email, "Your order for $" + std::to_string(amount) + " has been processed" ); } }};
// Easy to test with mocksclass MockNotificationService : public NotificationService {public: void sendNotification(const std::string& to, const std::string& message) override { std::cout << "[MOCK] Would send to " << to << ": " << message << std::endl; }};
class MockPaymentProcessor : public PaymentProcessor {public: bool processPayment(double amount, const std::map<std::string, std::string>& details) override { std::cout << "[MOCK] Would process $" << amount << std::endl; return true; }};
int main() { // Usage - Flexible and testable! // Can easily swap implementations EmailService emailService; StripePaymentProcessor stripeProcessor; OrderProcessor orderProcessor1(&emailService, &stripeProcessor);
// Switch to SMS and PayPal - no code changes needed! SMSService smsService; PayPalPaymentProcessor paypalProcessor; OrderProcessor orderProcessor2(&smsService, &paypalProcessor);
// Easy to test with mocks MockNotificationService mockNotification; MockPaymentProcessor mockPayment; OrderProcessor testProcessor(&mockNotification, &mockPayment); // Easy to test without real services!
return 0;}using System;using System.Collections.Generic;
// Abstractions - define what services can dopublic interface INotificationService{ // Send a notification void SendNotification(string to, string message);}
public interface IPaymentProcessor{ // Process a payment and return success status bool ProcessPayment(double amount, Dictionary<string, string> details);}
// Low-level modules - concrete implementationspublic class EmailService : INotificationService{ // Low-level module - email implementation public void SendNotification(string to, string message) { Console.WriteLine($"Sending email to {to}: {message}"); // Actual email sending logic... }}
public class SMSService : INotificationService{ // Low-level module - SMS implementation public void SendNotification(string to, string message) { Console.WriteLine($"Sending SMS to {to}: {message}"); // Actual SMS sending logic... }}
public class StripePaymentProcessor : IPaymentProcessor{ // Low-level module - Stripe implementation public bool ProcessPayment(double amount, Dictionary<string, string> details) { Console.WriteLine($"Processing ${amount} via Stripe"); // Actual Stripe API call... return true; }}
public class PayPalPaymentProcessor : IPaymentProcessor{ // Low-level module - PayPal implementation public bool ProcessPayment(double amount, Dictionary<string, string> details) { Console.WriteLine($"Processing ${amount} via PayPal"); // Actual PayPal API call... return true; }}
// High-level module - depends on abstractionspublic class OrderProcessor{ // ✓ High-level module - depends on abstractions private INotificationService notificationService; // ✓ Abstraction! private IPaymentProcessor paymentProcessor; // ✓ Abstraction!
public OrderProcessor( INotificationService notificationService, IPaymentProcessor paymentProcessor ) { this.notificationService = notificationService; this.paymentProcessor = paymentProcessor; }
// Process an order public void ProcessOrder(double amount, Dictionary<string, string> paymentDetails, string email) { // Process payment bool success = paymentProcessor.ProcessPayment(amount, paymentDetails);
if (success) { // Send confirmation notification notificationService.SendNotification( email, $"Your order for ${amount} has been processed" ); } }}
// Easy to test with mockspublic class MockNotificationService : INotificationService{ public void SendNotification(string to, string message) { Console.WriteLine($"[MOCK] Would send to {to}: {message}"); }}
public class MockPaymentProcessor : IPaymentProcessor{ public bool ProcessPayment(double amount, Dictionary<string, string> details) { Console.WriteLine($"[MOCK] Would process ${amount}"); return true; }}
class Program{ static void Main() { // Usage - Flexible and testable! // Can easily swap implementations EmailService emailService = new EmailService(); StripePaymentProcessor stripeProcessor = new StripePaymentProcessor(); OrderProcessor orderProcessor1 = new OrderProcessor(emailService, stripeProcessor);
// Switch to SMS and PayPal - no code changes needed! SMSService smsService = new SMSService(); PayPalPaymentProcessor paypalProcessor = new PayPalPaymentProcessor(); OrderProcessor orderProcessor2 = new OrderProcessor(smsService, paypalProcessor);
// Easy to test with mocks MockNotificationService mockNotification = new MockNotificationService(); MockPaymentProcessor mockPayment = new MockPaymentProcessor(); OrderProcessor testProcessor = new OrderProcessor(mockNotification, mockPayment); // Easy to test without real services! }}package main
import "fmt"
// Abstractionstype NotificationService interface { SendNotification(to, message string)}
type PaymentProcessor interface { ProcessPayment(amount float64, details map[string]string) bool}
// Low-level modulestype EmailService struct{}
func (s *EmailService) SendNotification(to, message string) { fmt.Printf("Sending email to %s: %s\n", to, message)}
type SMSService struct{}
func (s *SMSService) SendNotification(to, message string) { fmt.Printf("Sending SMS to %s: %s\n", to, message)}
type StripePaymentProcessor struct{}
func (p *StripePaymentProcessor) ProcessPayment(amount float64, details map[string]string) bool { fmt.Printf("Processing $%.2f via Stripe\n", amount) return true}
type PayPalPaymentProcessor struct{}
func (p *PayPalPaymentProcessor) ProcessPayment(amount float64, details map[string]string) bool { fmt.Printf("Processing $%.2f via PayPal\n", amount) return true}
// ✓ High-level module - depends on abstractionstype OrderProcessor struct { notification NotificationService // ✓ Abstraction! payment PaymentProcessor // ✓ Abstraction!}
func NewOrderProcessor(n NotificationService, p PaymentProcessor) *OrderProcessor { return &OrderProcessor{notification: n, payment: p}}
func (o *OrderProcessor) ProcessOrder(amount float64, details map[string]string, email string) { if o.payment.ProcessPayment(amount, details) { o.notification.SendNotification(email, fmt.Sprintf("Your order for $%.2f has been processed", amount)) }}
// Mock implementations for testingtype MockNotificationService struct{}
func (m *MockNotificationService) SendNotification(to, message string) { fmt.Printf("[MOCK] Would send to %s: %s\n", to, message)}
type MockPaymentProcessor struct{}
func (m *MockPaymentProcessor) ProcessPayment(amount float64, details map[string]string) bool { fmt.Printf("[MOCK] Would process $%.2f\n", amount) return true}
func main() { proc1 := NewOrderProcessor(&EmailService{}, &StripePaymentProcessor{})
// Switch to SMS and PayPal - no code changes needed! proc2 := NewOrderProcessor(&SMSService{}, &PayPalPaymentProcessor{})
// Easy to test with mocks testProc := NewOrderProcessor(&MockNotificationService{}, &MockPaymentProcessor{})
_ = proc1 _ = proc2 _ = testProc}trait OrderRepository { fn save_order(&self);}trait Notifier { fn send_confirmation(&self);}struct OrderService<R: OrderRepository, N: Notifier> { repository: R, notifier: N,}impl<R: OrderRepository, N: Notifier> OrderService<R, N> { fn process_order(&self) { self.repository.save_order(); self.notifier.send_confirmation(); }}struct MySqlRepository;struct EmailNotifier;impl OrderRepository for MySqlRepository { fn save_order(&self) { println!("Saved order"); }}impl Notifier for EmailNotifier { fn send_confirmation(&self) { println!("Email sent"); }}Why this follows DIP:
OrderProcessordepends onNotificationServiceandPaymentProcessorabstractions- Can easily swap implementations (Email → SMS, Stripe → PayPal)
- Easy to test - can inject mock implementations
- New services can be added without modifying
OrderProcessor - Loose coupling makes the system flexible and maintainable
Dependency Injection (DI) is a technique that helps implement DIP by:
- Injecting dependencies instead of creating them internally
- Making dependencies explicit through constructor parameters
- Enabling easy swapping of implementations
Without Dependency Injection (Bad)
Section titled “Without Dependency Injection (Bad)”class UserService: """❌ Creates dependencies internally""" def __init__(self): self.repository = DatabaseRepository() # ❌ Hard-coded dependency! self.logger = FileLogger() # ❌ Hard-coded dependency!
def get_user(self, user_id: int): self.logger.log(f"Getting user {user_id}") return self.repository.find(user_id)// ❌ Creates dependencies internallypublic class UserService { private DatabaseRepository repository; // ❌ Hard-coded dependency! private FileLogger logger; // ❌ Hard-coded dependency!
public UserService() { this.repository = new DatabaseRepository(); this.logger = new FileLogger(); }
public User getUser(int userId) { logger.log("Getting user " + userId); return repository.find(userId); }}// ❌ Creates dependencies internallyclass UserService { private repository: DatabaseRepository; // ❌ Hard-coded dependency! private logger: FileLogger; // ❌ Hard-coded dependency!
constructor() { this.repository = new DatabaseRepository(); this.logger = new FileLogger(); }
getUser(userId: number): any { this.logger.log(`Getting user ${userId}`); return this.repository.find(userId); }}#include <iostream>
class DatabaseRepository {public: void find(int userId) { std::cout << "Finding user " << userId << " in database" << std::endl; }};
class FileLogger {public: void log(const std::string& message) { std::cout << "Logging to file: " << message << std::endl; }};
// ❌ Creates dependencies internallyclass UserService {private: DatabaseRepository repository; // ❌ Hard-coded dependency! FileLogger logger; // ❌ Hard-coded dependency!
public: UserService() { // Dependencies created automatically }
void getUser(int userId) { logger.log("Getting user " + std::to_string(userId)); repository.find(userId); }};using System;
// ❌ Creates dependencies internallypublic class UserService{ private DatabaseRepository repository; // ❌ Hard-coded dependency! private FileLogger logger; // ❌ Hard-coded dependency!
public UserService() { this.repository = new DatabaseRepository(); this.logger = new FileLogger(); }
public object GetUser(int userId) { logger.Log($"Getting user {userId}"); return repository.Find(userId); }}package main
import "fmt"
// ❌ Creates dependencies internallytype UserService struct { repository *DatabaseRepository // ❌ Hard-coded dependency! logger *FileLogger // ❌ Hard-coded dependency!}
func NewUserService() *UserService { return &UserService{ repository: &DatabaseRepository{}, logger: &FileLogger{}, }}
func (s *UserService) GetUser(userID int) any { s.logger.Log(fmt.Sprintf("Getting user %d", userID)) return s.repository.Find(userID)}
type DatabaseRepository struct{}
func (r *DatabaseRepository) Find(id int) any { return nil }
type FileLogger struct{}
func (l *FileLogger) Log(msg string) { fmt.Println(msg) }struct EmailService;impl EmailService { fn send(&self, message: &str) { println!("Email: {message}"); }}struct UserService { email: EmailService,}impl UserService { fn new() -> Self { Self { email: EmailService, } } fn register(&self) { self.email.send("Welcome"); }}// UserService constructs its dependency.With Dependency Injection (Good)
Section titled “With Dependency Injection (Good)”class UserService: """✓ Receives dependencies via constructor""" def __init__(self, repository: UserRepository, logger: Logger): self.repository = repository # ✓ Injected dependency! self.logger = logger # ✓ Injected dependency!
def get_user(self, user_id: int): self.logger.log(f"Getting user {user_id}") return self.repository.find(user_id)
# Usage - Easy to swap implementationsdatabase_repo = DatabaseRepository()file_logger = FileLogger()service1 = UserService(database_repo, file_logger)
# Switch to in-memory repo and console loggermemory_repo = InMemoryRepository()console_logger = ConsoleLogger()service2 = UserService(memory_repo, console_logger) # ✓ No code changes!// ✓ Receives dependencies via constructorpublic class UserService { private UserRepository repository; // ✓ Injected dependency! private Logger logger; // ✓ Injected dependency!
public UserService(UserRepository repository, Logger logger) { this.repository = repository; this.logger = logger; }
public User getUser(int userId) { logger.log("Getting user " + userId); return repository.find(userId); }}
// Usage - Easy to swap implementationspublic class Main { public static void main(String[] args) { // Use database repo and file logger DatabaseRepository databaseRepo = new DatabaseRepository(); FileLogger fileLogger = new FileLogger(); UserService service1 = new UserService(databaseRepo, fileLogger);
// Switch to in-memory repo and console logger InMemoryRepository memoryRepo = new InMemoryRepository(); ConsoleLogger consoleLogger = new ConsoleLogger(); UserService service2 = new UserService(memoryRepo, consoleLogger); // ✓ No code changes! }}// ✓ Receives dependencies via constructorclass UserService { private repository: UserRepository; // ✓ Injected dependency! private logger: Logger; // ✓ Injected dependency!
constructor(repository: UserRepository, logger: Logger) { this.repository = repository; this.logger = logger; }
getUser(userId: number): any { this.logger.log(`Getting user ${userId}`); return this.repository.find(userId); }}
// Usage - Easy to swap implementationsconst databaseRepo = new DatabaseRepository();const fileLogger = new FileLogger();const service1 = new UserService(databaseRepo, fileLogger);
// Switch to in-memory repo and console loggerconst memoryRepo = new InMemoryRepository();const consoleLogger = new ConsoleLogger();const service2 = new UserService(memoryRepo, consoleLogger); // ✓ No code changes!#include <iostream>#include <string>
// Abstractionsclass UserRepository {public: virtual ~UserRepository() = default; virtual void find(int userId) = 0;};
class Logger {public: virtual ~Logger() = default; virtual void log(const std::string& message) = 0;};
// Concrete implementationsclass DatabaseRepository : public UserRepository {public: void find(int userId) override { std::cout << "Finding user " << userId << " in database" << std::endl; }};
class InMemoryRepository : public UserRepository {public: void find(int userId) override { std::cout << "Finding user " << userId << " in memory" << std::endl; }};
class FileLogger : public Logger {public: void log(const std::string& message) override { std::cout << "Logging to file: " << message << std::endl; }};
class ConsoleLogger : public Logger {public: void log(const std::string& message) override { std::cout << "Logging to console: " << message << std::endl; }};
// ✓ Receives dependencies via constructorclass UserService {private: UserRepository* repository; // ✓ Injected dependency! Logger* logger; // ✓ Injected dependency!
public: UserService(UserRepository* repository, Logger* logger) : repository(repository), logger(logger) {}
void getUser(int userId) { logger->log("Getting user " + std::to_string(userId)); repository->find(userId); }};
int main() { // Usage - Easy to swap implementations DatabaseRepository databaseRepo; FileLogger fileLogger; UserService service1(&databaseRepo, &fileLogger);
// Switch to in-memory repo and console logger InMemoryRepository memoryRepo; ConsoleLogger consoleLogger; UserService service2(&memoryRepo, &consoleLogger); // ✓ No code changes!
return 0;}using System;
// Abstractionspublic interface IUserRepository{ object Find(int userId);}
public interface ILogger{ void Log(string message);}
// Concrete implementationspublic class DatabaseRepository : IUserRepository{ public object Find(int userId) { Console.WriteLine($"Finding user {userId} in database"); return new object(); }}
public class InMemoryRepository : IUserRepository{ public object Find(int userId) { Console.WriteLine($"Finding user {userId} in memory"); return new object(); }}
public class FileLogger : ILogger{ public void Log(string message) { Console.WriteLine($"Logging to file: {message}"); }}
public class ConsoleLogger : ILogger{ public void Log(string message) { Console.WriteLine($"Logging to console: {message}"); }}
// ✓ Receives dependencies via constructorpublic class UserService{ private IUserRepository repository; // ✓ Injected dependency! private ILogger logger; // ✓ Injected dependency!
public UserService(IUserRepository repository, ILogger logger) { this.repository = repository; this.logger = logger; }
public object GetUser(int userId) { logger.Log($"Getting user {userId}"); return repository.Find(userId); }}
class Program{ static void Main() { // Usage - Easy to swap implementations DatabaseRepository databaseRepo = new DatabaseRepository(); FileLogger fileLogger = new FileLogger(); UserService service1 = new UserService(databaseRepo, fileLogger);
// Switch to in-memory repo and console logger InMemoryRepository memoryRepo = new InMemoryRepository(); ConsoleLogger consoleLogger = new ConsoleLogger(); UserService service2 = new UserService(memoryRepo, consoleLogger); // ✓ No code changes! }}package main
import "fmt"
// Abstractionstype UserRepository interface{ Find(userID int) any }type Logger interface{ Log(message string) }
// Concrete implementationstype DatabaseRepository struct{}
func (r *DatabaseRepository) Find(userID int) any { fmt.Printf("Finding user %d in database\n", userID) return struct{}{}}
type InMemoryRepository struct{}
func (r *InMemoryRepository) Find(userID int) any { fmt.Printf("Finding user %d in memory\n", userID) return struct{}{}}
type FileLogger struct{}
func (l *FileLogger) Log(msg string) { fmt.Printf("Logging to file: %s\n", msg) }
type ConsoleLogger struct{}
func (l *ConsoleLogger) Log(msg string) { fmt.Printf("Logging to console: %s\n", msg) }
// ✓ Receives dependencies via constructortype UserService struct { repository UserRepository // ✓ Injected dependency! logger Logger // ✓ Injected dependency!}
func NewUserService(repo UserRepository, logger Logger) *UserService { return &UserService{repository: repo, logger: logger}}
func (s *UserService) GetUser(userID int) any { s.logger.Log(fmt.Sprintf("Getting user %d", userID)) return s.repository.Find(userID)}
func main() { // Use database repo and file logger service1 := NewUserService(&DatabaseRepository{}, &FileLogger{}) service1.GetUser(1)
// Switch to in-memory repo and console logger - no code changes! service2 := NewUserService(&InMemoryRepository{}, &ConsoleLogger{}) service2.GetUser(1)}trait MessageSender { fn send(&self, message: &str);}struct UserService<S: MessageSender> { sender: S,}impl<S: MessageSender> UserService<S> { fn new(sender: S) -> Self { Self { sender } } fn register(&self) { self.sender.send("Welcome"); }}struct EmailService;impl MessageSender for EmailService { fn send(&self, message: &str) { println!("Email: {message}"); }}Benefits of Following DIP
Section titled “Benefits of Following DIP”Key Takeaways
Section titled “Key Takeaways”Remember: The Dependency Inversion Principle ensures that your system is flexible, testable, and maintainable by depending on abstractions rather than concrete implementations! 🎯