Module 4: Structural and Creational Patterns: Case Studies
These cases treat patterns as design tools, not decorations. Use them when construction, composition, or interface boundaries are creating real change pressure.
Case Study 1: Adapter for a Payment Provider Migration
Scenario: A checkout system uses Provider A's chargeCard(customerId, amountCents) API. Provider B uses payment intents, idempotency keys, and asynchronous confirmation. The team wants to migrate without rewriting checkout logic.
Source anchor: Design Patterns Catalog - Refactoring.Guru.
Module concepts:
- Adapter
- boundary interfaces
- third-party API isolation
- contract tests
Wrong Approach
Expose Provider B objects throughout checkout and update every caller to know payment intents.
Better Approach
Define the application's payment port first: authorize, capture, refund, and query status. Implement Provider A and Provider B adapters behind that contract, and write contract tests so both adapters obey the same application-level semantics.
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
| Direct provider use | Fast integration | Vendor lock-in across app |
| Adapter | Stable app contract | Mapping edge cases need tests |
| Full abstraction platform | Maximum flexibility | May overbuild early |
Failure Mode
Provider B's async confirmation leaks into checkout controllers and forces UI code to know provider-specific states.
Required Artifact
Write a payment adapter contract with operations, error mapping, idempotency behavior, and contract-test cases.
Project / Capstone Connection
Use this contract whenever the project touches an external API.
Case Study 2: Decorator for Caching and Rate Limits
Scenario: A product catalog client calls a slow remote API. Engineers add caching, logging, retries, and rate limiting directly into CatalogClient, making it difficult to test each concern.
Source anchor: Design Patterns Catalog - Refactoring.Guru.
Module concepts:
- Decorator
- composition
- cross-cutting behavior
- ordering effects
Wrong Approach
Keep adding flags to CatalogClient: useCache, logRequests, retry, respectRateLimit.
Better Approach
Keep a simple CatalogPort and wrap it with decorators such as CachedCatalog, RateLimitedCatalog, RetryingCatalog, and LoggingCatalog. Decide wrapper order deliberately because caching before rate limiting behaves differently from rate limiting before caching.
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
| Flags in one class | Fewer objects | Combinatorial complexity |
| Decorators | Isolated concerns | Wrapper order matters |
| Middleware framework | Consistent pipeline | More infrastructure |
Failure Mode
Retries happen inside the rate limiter and burst through the vendor limit.
Required Artifact
Draw a decorator stack diagram with call order, failure behavior, metrics emitted, and tests for order-sensitive behavior.
Project / Capstone Connection
Use the same stack diagram for caching, logging, or authorization wrappers.
Case Study 3: Facade for Report Generation
Scenario: Monthly finance reports require data from billing, tax, currency conversion, PDF rendering, and object storage. Controllers call all subsystems directly and duplicate orchestration.
Source anchor: Design Patterns Catalog - Refactoring.Guru.
Module concepts:
- Facade
- subsystem coordination
- API simplification
- orchestration boundary
Wrong Approach
Create a helper function in every controller that calls the same five services.
Better Approach
Introduce a FinanceReportFacade that exposes business-level operations such as generateMonthlyReport(period). Keep subsystem-specific detail inside the facade and return a stable result object with report URL, warnings, and audit ID.
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
| Direct subsystem calls | Flexible per caller | Duplication and coupling |
| Facade | Simple caller API | Can become a god object |
| Workflow engine | Strong orchestration | Heavy for simple reports |
Failure Mode
One controller skips the currency conversion step and publishes inconsistent totals.
Required Artifact
Create a facade API sketch with operations, dependencies hidden, return types, errors, and ownership boundaries.
Project / Capstone Connection
Use this when a feature coordinates several modules and callers do not need the details.
Case Study 4: Factory Method for Environment-Specific Clients
Scenario: Local development uses an in-memory email sender, staging uses a sandbox provider, and production uses a real provider with signing keys. Construction logic is scattered across app startup and tests.
Source anchor: Design Patterns Catalog - Refactoring.Guru.
Module concepts:
- Factory Method
- dependency creation
- environment configuration
- test seams
Wrong Approach
Add if env == "production" checks wherever an email sender is needed.
Better Approach
Centralize construction in an EmailSenderFactory or composition root. Return the same interface for every environment, validate configuration at startup, and keep tests from depending on production credentials.
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
| Inline construction | Easy local code | Scattered environment logic |
| Factory method | Central construction | Another abstraction to maintain |
| DI container | Consistent composition | Can hide simple wiring |
Failure Mode
A test accidentally sends real emails because one call site bypassed the environment check.
Required Artifact
Write an environment construction matrix covering local, test, staging, production, required configuration, and failure behavior.
Project / Capstone Connection
Use this matrix for constructing repositories, API clients, and fake implementations.
Case Study 5: Builder for Complex Export Configuration
Scenario: A data export job has optional compression, encryption, partitioning, retention policy, destination, schema version, and notification settings. Constructors are overloaded and call sites are unreadable.
Source anchor: Design Patterns Catalog - Refactoring.Guru.
Module concepts:
- Builder
- valid construction
- named options
- configuration readability
Wrong Approach
Create more constructors and rely on argument order.
Better Approach
Use a builder or typed configuration object that names every option, validates incompatible choices, and produces an immutable ExportJobConfig. Keep defaults explicit and test invalid combinations.
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
| Overloaded constructors | Familiar syntax | Ambiguous call sites |
| Builder | Readable construction | More code |
| Plain config object | Simple data shape | Needs validation discipline |
Failure Mode
A caller enables encryption without a key because no construction step validates the combination.
Required Artifact
Design a builder flow with required steps, optional defaults, invalid combinations, and example call sites.
Project / Capstone Connection
Use this for any project object that needs multiple optional but constrained settings.
Source Map
| Source | Use it for |
|---|---|
| Design Patterns Catalog - Refactoring.Guru | Pattern intent and vocabulary across creational and structural choices. |