Cohesion and dependency direction
Single responsibility means a cohesive reason to change, not one method per class. Dependency inversion puts stable business policy behind suitable contracts rather than tying it to transport or storage details. Interface segregation keeps clients from depending on capabilities they do not use. Open/closed design seeks useful extension boundaries without pretending future requirements can be predicted perfectly. Evaluate these principles through a concrete change.
Strategy, factory and builder solve different problems
A Strategy encapsulates alternative behavior behind one contract. A factory chooses or constructs an implementation. A builder assembles a value with readable configuration and validation. Dependency injection wires collaborators and lifecycle ownership. These ideas can work together, but they are not interchangeable labels. A small fixed conditional may be clearer than several types if no independent variation exists.
State and observation need lifecycle rules
A State design can make legal transitions explicit instead of permitting invalid combinations of flags. Observer-style notification needs subscription ownership, failure isolation and delivery timing. In-process callbacks are neither durable nor automatically asynchronous. Document who can trigger a transition, which side effects occur once and what happens when a collaborator fails halfway through. Patterns help express those contracts; they do not supply them automatically.
Worked Java example
Illustrative snippet: imports, surrounding types or collaborators may be omitted. Read the assumptions before adapting it.
interface DiscountPolicy {
BigDecimal discount(BigDecimal subtotal);
}
class Checkout {
private final DiscountPolicy policy;
Checkout(DiscountPolicy policy) { this.policy = policy; }
BigDecimal total(BigDecimal subtotal) {
return subtotal.subtract(policy.discount(subtotal));
}
}Walkthrough
Checkout delegates only the discount decision. A production contract should require a nonnegative discount no greater than the eligible subtotal and define currency/rounding. If policy violations are possible, validate at the boundary. The caller can test checkout with a deterministic policy without inheriting from Checkout or connecting to a promotion service.
Practice it yourself
An application supports email and SMS notifications. Where would you put provider selection, retry policy and message formatting?
Check your reasoning
Separate formatting and transport where those concerns vary independently. A factory or configuration can select a transport; bounded retries and idempotency belong to delivery coordination rather than every formatter. Keep the contracts small and specify whether success means accepted or delivered.
Recall checklist
- Explain the main invariant or contract without looking.
- Trace the example and test an edge case.
- State time, space, input variables and assumptions where relevant.
- Explain one alternative and when you would choose it.