Encapsulation, Inheritance, and Polymorphism

Encapsulation hides implementation details behind a stable API. Inheritance should model a genuine "is-a" relationship. Polymorphism allows code to depend on an abstraction while the runtime object determines the implementation.

interface PaymentProcessor {
    void pay(long cents);
}

final class CardPayment implements PaymentProcessor {
    public void pay(long cents) {
        System.out.println("Card: " + cents);
    }
}

final class Checkout {
    private final PaymentProcessor processor;

    Checkout(PaymentProcessor processor) {
        this.processor = processor;
    }

    void complete(long cents) {
        processor.pay(cents);
    }
}

Checkout does not care whether payment uses a card, wallet, or test double. This is dependency inversion through an interface.

Prefer composition

Inheritance creates tight coupling. Use composition when the relationship is really 'has-a' rather than 'is-a'.

Key Takeaways
  • Polymorphism reduces coupling to concrete implementations.
  • Interfaces are excellent boundaries for dependency injection and testing.
  • Composition is often more flexible than deep inheritance hierarchies.