Loading
Loading
Design Principles
Composition over inheritance, programming to interfaces, encapsulating variation, and knowing when not to.
By the end of this chapter
This is the most actionable principle in the set, because it is a procedure rather than an aspiration. Walk the requirements, and for each one ask: has this changed before, or is it on the roadmap? Those are your variation points. Everything else is stable, and stable things do not need seams.
Depend on the narrowest type that does the job. A parameter typed List<Order> when the method only iterates should be Iterable<Order>: it accepts more callers and promises less, which is the right direction on both counts.
Dependency injection means a class is given its collaborators rather than constructing them. That is the whole idea; a container is an optional convenience for wiring, not the principle.
// Not injected: impossible to test without a real clock and gateway.final class Booking { private final Clock clock = Clock.systemUTC(); private final PaymentGateway gateway = new StripeGateway();} // Injected: every collaborator is visible in the signature, and// substitutable in a test.final class Booking { private final Clock clock; private final PaymentGateway gateway; Booking(Clock clock, PaymentGateway gateway) { this.clock = clock; this.gateway = gateway; }}An immutable object cannot be in an invalid state after construction, cannot be changed by a method you passed it to, and is safe to share between threads. Those three properties remove entire categories of bug.
The cost is allocation on every change, which matters for large objects mutated in a tight loop and essentially never elsewhere. Value-like things — money, dates, identifiers, coordinates, events — should be immutable by default. Long-lived entities with genuine identity and lifecycle are the reasonable exception.
Sign in to keep a note here. It is free, and nobody else can see it.
Sign inWhat this chapter was for: