11. Integrating and Simplifying: Adapter and Façade
These are the two patterns you and your agent will reach for most, because most real work is integration: making someone else’s code fit yours, and taming a subsystem that has grown too many moving parts. Adapter and Façade are both about wrapping — one to make code fit, the other to make a subsystem simple. The discipline that keeps both honest is a single question: does the wrapper hide more than it shows?
The Itch
Section titled “The Itch”checkout-lite has two integration pains, and they are the everyday texture of modern coding.
First, payments. Chapters 8 and 10 built our own gateways behind IPaymentGateway,
whose contract is charge(amount: float) -> Receipt. Now the business wants Stripe,
and the SDK looks nothing like ours:
class StripeClient: # third-party — we don't own a line of it def create_charge(self, amount_cents: int, currency: str = "usd") -> dict: ...class StripeClient { // third-party — we don't own a line of it createCharge(amountCents: number, currency = "usd"): Record<string, unknown> { /* ... */ }}Different method name, money in cents, a raw dict / Record back. We can’t make
their class satisfy our interface by inheritance, and we really don’t want
cents-and-records leaking into pricing, receipts, and refunds across the codebase.
Second, the checkout flow itself. Placing an order now means orchestrating four subsystems in the right order — price it, charge it, notify the customer, save it — and every caller (the web handler, the admin tool, the test fixture) has to know the whole dance. The complexity is real, but no caller should have to learn it to place one order.
The Concept
Section titled “The Concept”Adapter — make it fit
Section titled “Adapter — make it fit”The Adapter pattern puts a translating layer between an interface you expect (the target) and an object that doesn’t match it (the adaptee). The adapter implements the target interface and, inside each method, delegates to the adaptee while translating the mismatch — names, types, units, return shapes.
classDiagram
class IPaymentGateway {
<<interface>>
+charge(amount) Receipt
}
class StripeGateway {
+charge(amount) Receipt
}
class StripeClient {
+create_charge(cents) dict
}
IPaymentGateway <|.. StripeGateway
StripeGateway o--> StripeClient : translates to
The rest of checkout-lite keeps talking to IPaymentGateway; only StripeGateway
ever sees Stripe. That is the adapter earning its keep — it doesn’t just rename a
method, it translates dollars to cents and a dict to a Receipt. An adapter that
only forwarded a call unchanged would be a pass-through (Chapter 9), not a design.
Façade — make it simple
Section titled “Façade — make it simple”The Façade pattern puts one simple interface in front of a whole subsystem, coordinating its parts so callers don’t have to. It doesn’t change what the subsystem can do; it changes how much you must know to use it.
classDiagram
class place_order {
+place_order(order) Receipt
}
class pricing
class gateway
class notify
class persistence
place_order ..> pricing
place_order ..> gateway
place_order ..> notify
place_order ..> persistence
This is, in John Ousterhout’s terms, a depth-adding move: a tiny interface
(place_order) over real functionality (four coordinated subsystems). A façade earns
its place by depth — if it merely forwarded each subsystem call one-to-one without
coordinating or simplifying, it would be another pass-through.
Before / After
Section titled “Before / After”Adapter
Section titled “Adapter”Before
Section titled “Before”The SDK’s shape leaks everywhere a payment happens:
# The SDK's shape leaks everywhere a payment happens:client = StripeClient()resp = client.create_charge(int(order_total * 100)) # remember: cents!if resp["paid"]: confirmation = resp["id"] # dig through the dict# ...and the same cents-and-dict ritual is copied into refunds, retries, admin...// The SDK's shape leaks everywhere a payment happens:const client = new StripeClient();const resp = client.createCharge(Math.round(orderTotal * 100)); // remember: cents!if (resp.paid) { confirmation = String(resp.id); // dig through the record}// ...and the same cents-and-record ritual is copied into refunds, retries, admin...The adapter implements our interface and translates inside, so Stripe stays invisible everywhere else:
class StripeGateway(IPaymentGateway): def __init__(self, client: StripeClient) -> None: self._client = client
def charge(self, amount: float) -> Receipt: resp = self._client.create_charge(int(round(amount * 100))) # dollars → cents return Receipt("stripe", amount, resp["id"]) # dict → Receipt
# everywhere else, Stripe is invisible:gateway: IPaymentGateway = StripeGateway(StripeClient())receipt = gateway.charge(order_total)class StripeGateway implements IPaymentGateway { constructor(private readonly client: StripeClient) {}
charge(amount: number): Receipt { const resp = this.client.createCharge(Math.round(amount * 100)); // dollars → cents return { provider: "stripe", amount, confirmation: String(resp.id) }; // record → Receipt }}
// everywhere else, Stripe is invisible:const gateway: IPaymentGateway = new StripeGateway(new StripeClient());const receipt = gateway.charge(orderTotal);Façade
Section titled “Façade”Before
Section titled “Before”Every caller orchestrates the whole subsystem by hand:
# every caller orchestrates the whole subsystem by hand:total = order_total(order)confirmation = charge(total)send_receipt(order, total)save_order(order, total)receipt = Receipt(total, confirmation)// every caller orchestrates the whole subsystem by hand:const total = orderTotal(order);const confirmation = charge(total);sendReceipt(order, total);saveOrder(order, total);const receipt = { total, confirmation };One friendly door coordinates the four steps; callers learn only the door:
def place_order(order: Order) -> Receipt: # the one friendly door total = order_total(order) confirmation = charge(total) send_receipt(order, total) save_order(order, total) return Receipt(total, confirmation)
receipt = place_order(order) # callers know only thisconst placeOrder = (order: Order): Receipt => { // the one friendly door const total = orderTotal(order); const confirmation = charge(total); sendReceipt(order, total); saveOrder(order, total); return { total, confirmation };};
const receipt = placeOrder(order); // callers know only thisThe full code, with tests that prove the adapter translates and the façade drives the
whole flow in one call, is in examples/ch11/py/ (Python) and examples/ch11/ts/
(TypeScript).
Choosing between Adapter and Façade
Section titled “Choosing between Adapter and Façade”They are constantly confused, because both wrap. The difference is what they change:
| Adapter | Façade | |
|---|---|---|
| Changes | The shape of an interface | The amount you must know |
| Functionality | Same — just made to fit | Same — just made simple |
| Wraps | Usually one adaptee | A whole subsystem |
| One-liner | ”Make it fit" | "Make it simple” |
If you’re matching a square peg to a round hole, it’s an Adapter. If you’re putting a reception desk in front of a busy office, it’s a Façade. And both face the same test: the wrapper must hide more than it exposes — otherwise it’s a pass-through with a pattern’s name on it.
Language Notes
Section titled “Language Notes”The two languages reach the same design from different starting points — and where they differ is exactly the lesson about what the pattern really is.
Here is where the book’s interface rule (Chapter 8) pays off. You own your gateways,
so they inherit the IPaymentGateway ABC. But the third-party SDK can’t inherit
anything of yours — so you depend on a Protocol, which matches by shape
instead of by name:
@runtime_checkableclass SupportsCharge(Protocol): def charge(self, amount: float) -> Receipt: ...This buys two things. If a foreign object already has the shape you need, it
satisfies the Protocol with no adapter at all — structural typing plus duck
typing. And when it doesn’t (Stripe’s create_charge is not charge), your adapter
conforms to the shape without nominal inheritance, and a type checker verifies it.
You control both sides → ABC; you’re retrofitting code you don’t → Protocol.
For Façade, the Pythonic form is often not a class at all. A module is a façade:
a module-level place_order() function is “one friendly door,” and a curated
__init__.py that re-exports a small public API is a façade over a whole package
(more on that in Chapter 16). Reach for a Façade class only when the simplified
interface needs to hold state across calls.
TypeScript’s types are structural, which makes the Adapter’s central insight
native to the language. An interface is the target; nothing has to inherit it. A
foreign object that already has the shape is the type, with no implements and no
wrapper:
interface SupportsCharge { charge(amount: number): Receipt;}
// InHouseGateway declares no `implements`, inherits nothing of ours —// yet it satisfies SupportsCharge by shape alone, so it needs no adapter.const gateway: SupportsCharge = new InHouseGateway();This is the same payoff Protocol brings to Python, but it is the default rather than
an opt-in. When the foreign object doesn’t match (Stripe’s createCharge is not
charge), you write a StripeGateway that implements IPaymentGateway — here the
keyword is for intent, documenting the contract; the compiler would have accepted
the shape regardless. The decision rule is the same: you own both sides, declare the
interface and implements it; you’re retrofitting code you don’t, lean on the
structural match.
For Façade, the idiomatic form is often not a class at all. A free placeOrder
function is “one friendly door,” and a module’s barrel index.ts that re-exports a
small public API is a façade over a whole package (more on that in Chapter 16). Reach
for a Façade class only when the simplified interface must hold state across calls.
When NOT to Use
Section titled “When NOT to Use”🤖 AI Collaboration
Section titled “🤖 AI Collaboration”Gluing libraries together is the single most common thing an agent does, which makes these the patterns it reaches for most — and the ones it most often builds as pass-throughs. Your review job is to check for translation and depth, not just for a wrapper.
Vocabulary
Section titled “Vocabulary”| You say | The agent hears |
|---|---|
| ”Adapt this SDK to our interface” | Implement our target interface; translate to the adaptee inside |
| ”Depend on a structural shape (Protocol / interface)“ | Don’t inherit their class; match by shape, verified by the type checker |
| ”Put a Façade over this subsystem” | One simpler entry point that coordinates the parts |
| ”One entry point” | Callers should learn one call, not the orchestration |
| ”Does this wrapper add depth?” | Reject pass-throughs that rename or forward 1:1 |
Prompt templates
Section titled “Prompt templates”Wrap this third-party [SDK] behind our [IPaymentGateway] with an adapter. The
adapter implements our interface and translates to the SDK (names, units, return
types) so the SDK’s shape never appears elsewhere. Depend on a structural shape
(a Protocol in Python, a plain interface in TypeScript) rather than making their
class inherit ours. Don’t add a layer that only renames.
These call sites orchestrate [pricing/gateway/notify/persistence] by hand. Add a
Façade — a single place_order(order) function (a module-level function, not a
class, unless it must hold state) that coordinates them and returns the result. It
should reduce what a caller must know, not re-expose every subsystem method.
Review checklist
Section titled “Review checklist”- Does the adapter translate (names, units, shapes), not just rename?
- For third-party code, does it depend on a structural shape (Protocol / interface) rather than inheriting?
- Are you adapting code you don’t own? (If you own it, change it instead.)
- Does the façade simplify — fewer, cleaner calls — rather than forward 1:1?
- Does each wrapper hide more than it exposes (Ousterhout’s depth test)?
Agent failure modes
Section titled “Agent failure modes”- The pass-through wrapper. An “adapter” or “façade” that forwards calls unchanged — ceremony with no depth. The most common failure for both patterns.
- Inheritance for foreign code. The agent tries to make a third-party class
inherit your base class (it can’t, cleanly) instead of depending on a structural
shape (a
Protocol/ interface). - The leaky façade. A façade whose signature re-exposes the whole subsystem — a directory, not a door.
- Adapting your own code. Wrapping code it could simply edit, adding a layer for nothing.
Pattern Cheat Sheet
Section titled “Pattern Cheat Sheet”Key Takeaways
Section titled “Key Takeaways”- Adapter changes an interface’s shape to fit what a client expects; Façade reduces how much a client must know about a subsystem. Make it fit vs. make it simple.
- Adapter is for code you don’t own and must translate — names, units, return
shapes. This is where structural typing shines (a
Protocolin Python, a plain interface in TypeScript): depend on a shape, and a foreign class that already matches needs no adapter at all. - The lightweight Façade is often a module-level function or a curated barrel
module (
__init__.py/index.ts), not a class. - Both collapse into Chapter 9’s pass-through when they add no depth. Ousterhout’s test decides it: a wrapper must hide more than it exposes.
- These are the AI era’s workhorse patterns — and the ones agents most often build as ceremony. Review for translation and depth.
- Glossary terms added: Adapter · Façade · Protocol (structural typing).