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.