04. Structural patterns — connect rooms without breaking the whole building¶
~15 min read. Sometimes the code is fine; only the connections are clumsy.
Built on the ELI5 in 00-eli5.md. The hallway — interface or contract between classes — can be reshaped without rebuilding every room.
1) Why structural patterns exist at all¶
See. Many systems do not fail because classes are missing. They fail because class connections are awkward. One API returns ugly data. One object needs extra behavior. One subsystem is too noisy for callers.
Structural patterns solve connection problems. They help us fit existing rooms together. They do not usually invent new business rules. They change shape, wrapping, access, or composition.
A quick map helps.
Need old API to match new contract? → Adapter
Need extra behavior around same object? → Decorator
Need controlled access or lazy access? → Proxy
Need one simple door to big subsystem? → Facade
Need tree of parts and wholes? → Composite
Simple, no? Think of these as special hallways and wall fittings. The building mostly stays the same. Movement becomes cleaner.
2) Adapter and Facade: make entry simple¶
Adapter is translation. One side speaks one contract. The other side speaks another. Adapter stands in the middle.
Example: old payment SDK. Your app expects charge(amount). Vendor gives makePayment(currency, paise). So what to do? Write an adapter.
interface PaymentGateway {
PaymentResult charge(int rupees);
}
class LegacyBankSdk {
BankResponse makePayment(String currency, int paise) { ... }
}
class LegacyBankAdapter implements PaymentGateway {
private final LegacyBankSdk sdk;
public PaymentResult charge(int rupees) {
BankResponse response = sdk.makePayment("INR", rupees * 100);
return PaymentResult.from(response);
}
}
See the win. The ugly shape stays near the vendor boundary. The rest of the codebase sees one clean hallway.
Facade is different. Facade does not mainly translate. Facade simplifies a messy subsystem behind one small entry point.
Example: travel booking confirmation. Behind the scenes you call payment, inventory, invoice, notification, and loyalty systems. Caller should not know five steps. Give one facade.
class BookingFacade {
BookingConfirmation confirm(BookingRequest request) {
// reserve room
// charge payment
// generate invoice
// send notification
// update loyalty points
}
}
Mental picture:
Without facade
Caller → PaymentService
Caller → InventoryService
Caller → InvoiceService
Caller → NotificationService
With facade
Caller → BookingFacade → subsystem
Facade reduces caller noise. It also protects the floor plan from sprawling dependencies.
3) Decorator and Proxy: same door, extra behavior¶
Decorator wraps an object and adds behavior before or after the real call, while keeping the same interface. Very useful for logging, caching, retries, compression.
interface PriceService {
int getPrice(String sku);
}
class BasePriceService implements PriceService { ... }
class CachingPriceService implements PriceService {
private final PriceService inner;
private final Map<String, Integer> cache = new HashMap<>();
public int getPrice(String sku) {
if (cache.containsKey(sku)) return cache.get(sku);
int price = inner.getPrice(sku);
cache.put(sku, price);
return price;
}
}
Same interface. Extra behavior. No caller changes. That is classic decorator value.
Proxy also wraps an object. But the intent differs. Proxy controls access, location, or lifecycle. Examples: lazy loading, permission checks, remote calls.
class SecureDocumentProxy implements DocumentService {
private final DocumentService inner;
private final PermissionChecker checker;
public Document read(String docId, User user) {
checker.ensureCanRead(user, docId);
return inner.read(docId, user);
}
}
Decorator says, "same service, extra feature." Proxy says, "same service, guarded or indirect access." That distinction matters in interviews.
Worked number example. Suppose product price lookup hits DB in 8 ms. A page asks for 20 SKUs. Without caching decorator, cost is roughly 160 ms. With 70 percent cache hit rate, only 6 calls hit DB. Now cost falls near 48 ms plus cache overhead. That is visible user value.
4) Composite: treat one item and many items similarly¶
Composite shines when data is naturally tree-shaped. Folders and files. Menu and menu items. Org chart and employee nodes. Comments and replies.
The trick is one common interface. Leaf and group both implement it. Caller treats both uniformly.
interface CartItem {
int price();
}
class ProductItem implements CartItem {
public int price() { return 250; }
}
class BundleItem implements CartItem {
private final List<CartItem> items;
public int price() {
return items.stream().mapToInt(CartItem::price).sum();
}
}
Now caller can ask price() on one item or bundle. No special-case branching outside. That keeps hallways small and consistent.
Quick diagram:
When should you not use Composite? When the structure is not really recursive. Do not force a tree where list processing is enough. Pattern fit, remember.
5) Fast selection guide¶
Use this guide when confusion comes.
- Need contract translation → Adapter.
- Need simpler front door → Facade.
- Need add-on behavior → Decorator.
- Need guarded or lazy access → Proxy.
- Need part-whole tree → Composite.
A final example set helps.
- CSV library to domain parser → Adapter.
- Checkout subsystem entry point → Facade.
- Cache around recommendation service → Decorator.
- Auth wrapper around admin APIs → Proxy.
- Nested comment thread rendering → Composite.
See the pattern family now. They mostly protect caller simplicity. That is why they matter in production. Not because interview books love naming things.
Where this lives in the wild¶
- At Airbnb, a backend engineer uses facade-style booking flows to hide pricing, inventory, and messaging subsystems.
- At Paytm, a payments engineer writes bank SDK adapters so checkout code sees one gateway contract.
- At Slack, a platform engineer decorates API clients with retry and rate-limit metrics wrappers.
- At Dropbox, a storage engineer uses proxy objects for lazy file metadata loading from remote services.
- At Figma, a frontend platform engineer models nested layers and groups with composite-style trees.
Pause and recall¶
- What is the real difference between Adapter and Facade?
- Why are Decorator and Proxy similar in structure but different in intent?
- When is Composite better than many
instanceofchecks? - Which pattern keeps vendor ugliness near the integration edge?
Interview Q&A¶
Why Adapter not change the vendor SDK directly?¶
Because external APIs are not yours to reshape safely. Adapter protects your code from that unstable boundary. Common wrong answer to avoid: "Just rename the methods in the SDK wrapper class."
Why Facade not let callers orchestrate every subsystem call?¶
Because repeated orchestration leaks complexity and duplicates workflows. A facade gives one simpler entry for common flows. Common wrong answer to avoid: "Facade is only a helper class with static methods."
Why Decorator not subclass every service variant?¶
Because combinations of added behavior explode with inheritance. Wrapping composes features more flexibly. Common wrong answer to avoid: "Decorator is mainly for UI libraries."
Why Proxy not Decorator for access control?¶
Because the core intent is controlled access to the real subject. Extra behavior is secondary there. Common wrong answer to avoid: "Anything wrapping another object is decorator."
Apply now (5 min)¶
Exercise: Take a MapsClient whose vendor returns latitude and longitude in a strange nested JSON format. Choose Adapter, Facade, Decorator, Proxy, or Composite. Write two lines
explaining why the other four are weaker fits.
Sketch from memory: Draw the five-pattern selection guide. Then add one example from payments, one from content, and one from admin tools.
Bridge. Clean structure is not enough; now we need patterns for actions, events, and changing behavior. → 05-behavioral-patterns.md