Design Pattern

Dependency Inversion Principle (DIP)

Clean Java-only production-ready implementation.


Core Idea

High-level modules should not depend on low-level modules. Both should depend on abstractions.

The Violation

BookingService directly creates a PostgresSeatInventory and StripePaymentGateway. Switching databases or payment providers requires modifying BookingService.

How This Plays in an Interview

Interviewer: "What if we switch from Stripe to Razorpay?"

  • Without DIP: "Let me modify BookingService to call Razorpay instead of Stripe."
  • With DIP: "I implement RazorpayPayment implements PaymentPort. The BookingService doesn't change. I just inject a different implementation at startup."

When to Apply DIP in LLD Problems

ProblemWhat's the Port?What Implements It?
Parking Lot (#1)PricingStrategyHourlyPricing, WeekendPricing, SeasonalPricing
Splitwise (#5)ExpenseStorageInMemoryStore, SQLiteStore, CloudSyncStore
Vending Machine (#7)PaymentHandlerCoinPayment, CardPayment, UPIPayment
Notification (#18)NotificationChannelEmailChannel, SMSPush, WebSocketPush
Cache (#31)EvictionPolicyLRUEviction, LFUEviction, TTLEviction

Interview Checklist

  1. Identify external dependencies (DB, payment, APIs, filesystem, notifications)
  2. Define boundary interfaces (PORTS) from the HIGH-LEVEL module's perspective
  3. Implement ADAPTORS as low-level classes
  4. Wire everything at composition root (never in the high-level module)
  5. Test high-level logic with fake/mock adaptors

The Fix

BookingService depends on SeatInventoryPort, PaymentPort, NotificationPort โ€” interfaces defined by the high-level module. Low-level implementations (Postgres, Stripe, Twilio) implement these ports.

// โ”€โ”€โ”€ EXAMPLE 1 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// WHAT WE ARE IMPLEMENTING:
// A user manager service decouples business logic from specific database
// engine classes.
//
// WHERE THE PRINCIPLE FITS IN:
// High-level UserService depends on the Database abstraction interface rather
// than concrete MySQLDatabase or MongoDBDatabase implementations, enabling
// seamless driver swaps.
// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// --- High-level module defines the PORTS (abstractions) ---
interface SeatInventoryPort {
    boolean reserve(String seatId, String userId);
    void release(String seatId);
}

interface PaymentPort {
    boolean charge(String userId, double amount);
    void refund(String transactionId);
}

interface NotificationPort {
    void send(String userId, String message);
}

// --- High-level business logic depends ONLY on abstractions ---
class BookingService {
    private final SeatInventoryPort inventory;
    private final PaymentPort payment;
    private final NotificationPort notif;

    public BookingService(SeatInventoryPort inv, PaymentPort pay, NotificationPort notif) {
        this.inventory = inv;
        this.payment = pay;
        this.notif = notif;
    }

    public boolean bookTicket(String userId, String seatId, double price) {
        if (!inventory.reserve(seatId, userId)) {
            System.out.println("  [Booking] Reservation failed: " + seatId);
            return false;
        }
        boolean paid = payment.charge(userId, price);
        if (!paid) {
            inventory.release(seatId);
            System.out.println("  [Booking] Payment failed, released seat");
            return false;
        }
        notif.send(userId, "Booking confirmed for seat " + seatId);
        System.out.println("  [Booking] Success: " + seatId);
        return true;
    }
}

// --- Low-level implementations (injected, not instantiated) ---
class PostgresSeatInventory implements SeatInventoryPort {
    public boolean reserve(String seatId, String userId) {
        System.out.println("  [Postgres] Reserv seat=" + seatId + " user=" + userId);
        return true;
    }
    public void release(String seatId) {
        System.out.println("  [Postgres] Released seat=" + seatId);
    }
}

class StripePaymentGateway implements PaymentPort {
    public boolean charge(String userId, double amount) {
        System.out.println("  [Stripe] Charged " + amount + " for " + userId);
        return true;
    }
    public void refund(String transactionId) {
        System.out.println("  [Stripe] Refunded " + transactionId);
    }
}

class TwilioNotificationService implements NotificationPort {
    public void send(String userId, String message) {
        System.out.println("  [Twilio] SMS to " + userId + ": " + message);
    }
}

// --- Composition root (wire dependencies) ---
public class Main {
    public static void main(String[] args) {
        SeatInventoryPort inv = new PostgresSeatInventory();
        PaymentPort pay = new StripePaymentGateway();
        NotificationPort notif = new TwilioNotificationService();

        BookingService booking = new BookingService(inv, pay, notif);
        booking.bookTicket("user-42", "A12", 250.0);
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...