Design Pattern

Dependency Inversion Principle (DIP)

Clean Java-only production-ready implementation.


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

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

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);
    }
}