The hardest part of design patterns is not implementation. It is choosing the right pattern and stopping before abstraction becomes ceremony.
This final post ties the series together around one practical question:
How do patterns combine inside a real Java application?
Checkout System Example
A practical checkout flow might use:
Facadefor the application entry pointStrategyfor discount calculationAdapterfor payment providersObserverfor post-order notificationsChain of Responsibilityfor request validationBuilderfor assembling the final response object
Combination Diagram
flowchart LR
A[Checkout API] --> B[Validation Chain]
B --> C[Checkout Facade]
C --> D[Discount Strategy]
C --> E[Payment Adapter]
C --> F[Response Builder]
C --> G[Order Event Publisher]
G --> H[Email Observer]
G --> I[Inventory Observer]
G --> J[Loyalty Observer]
Implementation Walkthrough
public final class CheckoutApplicationService {
private final ValidationHandler validationHandler;
private final CheckoutFacade checkoutFacade;
public CheckoutApplicationService(ValidationHandler validationHandler,
CheckoutFacade checkoutFacade) {
this.validationHandler = validationHandler;
this.checkoutFacade = checkoutFacade;
}
public CheckoutResult submit(OrderRequest request) {
validationHandler.handle(request);
return checkoutFacade.checkout(request.toCommand());
}
}
Notice what each pattern is doing:
- the chain rejects invalid requests early
- the facade simplifies subsystem coordination
- strategies and adapters hide policy and integration variation
- observers decouple side effects
This is the correct way to think about patterns: as focused tools that solve different design pressures in one coherent flow.
The most important discipline is keeping those pattern roles separate. If the facade starts implementing discount policy, or the adapter starts making validation decisions, the patterns stop clarifying the design and start competing with each other.
Selection Heuristic
Use this matrix:
- if creation varies, look at creational patterns
- if integration or wrapping varies, look at structural patterns
- if runtime algorithm choice or lifecycle behavior varies, look at behavioral patterns
- if a plain refactor solves the problem, skip the pattern
Final Rule
Do not ask, “Which pattern can I apply here?” Ask, “What kind of change is making this code difficult to evolve?”
Once that is clear, the right pattern is usually obvious.