Interfaces: plug sockets before wiring rooms
Code against the smallest surface your caller needs
When a method parameter is typed as List<String> rather than ArrayList<String>, you buy the right to swap in a LinkedList, a read-only view, or a mocked fake in tests — without changing callers. Interfaces are the sockets that your rooms plug into.
public interface PaymentProcessor {
PaymentResult charge(Customer c, Money amount);
}
public class StripeProcessor implements PaymentProcessor { /* ... */ }
public class FakeProcessor implements PaymentProcessor { /* ... */ }
class CheckoutService {
private final PaymentProcessor processor;
CheckoutService(PaymentProcessor processor) { this.processor = processor; }
// swap StripeProcessor for FakeProcessor in tests — CheckoutService does not care
}Default methods since Java 8
Interfaces may now carry implementations via default methods. This is meant to let APIs evolve without breaking every implementor — not as a replacement for abstract classes. Keep default methods small and orthogonal.
- Prefer one cohesive interface per client role (
PaymentProcessor, notMegaGodInterface). - Keep methods narrow:
charge(Money amount)beatscharge(String jsonBlob). - Seal hierarchies with
sealedinterfaces when only a fixed set of implementations is valid.
Takeaways
- Program to the narrowest interface the call-site needs.
- Constructor-inject the interface; let the concrete class vary per environment.
- Default methods exist to evolve APIs safely, not to smuggle business logic into interfaces.
Enjoying This Lesson?
Your support helps create more comprehensive courses and lessons like this one. Help me build better learning experiences for everyone.
Support Awashyak