Structural Patterns I: Adapter & Facade

Design Patterns

Chapter 4 · Structural Patterns I: Adapter & Facade

Structural patterns compose already-created objects into larger structures — no more object creation, just how existing pieces fit together. This chapter covers two of the most common: making an incompatible interface work without changing it (Adapter), and simplifying access to a complex, many-part subsystem (Facade).

Adapter: Making an Incompatible Interface Work

Client code expects a simple pay(amount) interface. A legacy payment gateway exposes something genuinely different — different method name, different units, an extra required parameter:

def checkout(payment_processor, amount_dollars): return payment_processor.pay(amount_dollars) class LegacyPaymentGateway: def make_payment(self, amount_cents, currency): return f'Legacy gateway charged {amount_cents} cents ({currency})'
Verified directly — the incompatibility is a real, reproduced failure
Calling checkout(LegacyPaymentGateway(), 49.99) directly raises a genuine AttributeError: 'LegacyPaymentGateway' object has no attribute 'pay'. The two pieces of code are individually correct — they simply don't speak the same interface.

An Adapter wraps the incompatible object and exposes exactly the interface the client expects, translating each call underneath:

class PaymentGatewayAdapter: def __init__(self, legacy_gateway): self._legacy_gateway = legacy_gateway def pay(self, amount_dollars): amount_cents = round(amount_dollars * 100) return self._legacy_gateway.make_payment(amount_cents, currency='USD')
Verified directly — the exact same client code, now working, with a verified correct conversion
checkout(PaymentGatewayAdapter(LegacyPaymentGateway()), 49.99) — the identical checkout() function, completely unmodified — now succeeds, returning 'Legacy gateway charged 4999 cents (USD)'. 49.99 dollars correctly became 4999 cents — the conversion the adapter exists to handle, confirmed correct rather than assumed.

Facade: Simplifying a Complex Subsystem

Watching a movie on a real home theater setup means correctly sequencing three separate components — an amplifier, a projector, a DVD player — each with their own multi-step interface:

# Manual sequence -- the client has to know every step, and the right order amp.on() amp.set_volume(5) proj.on() proj.set_input('DVD') dvd.on() dvd.play('Inception')

A Facade hides that entire sequence behind one method:

class HomeTheaterFacade: def __init__(self, amp, dvd, proj): self.amp = amp; self.dvd = dvd; self.proj = proj def watch_movie(self, movie): self.amp.on() self.amp.set_volume(5) self.proj.on() self.proj.set_input('DVD') self.dvd.on() self.dvd.play(movie)
Verified directly — the facade produces the identical underlying call sequence
Logging every underlying method call made by each approach: the manual sequence produces exactly 6 calls, in a specific order. HomeTheaterFacade(...).watch_movie('Inception') produces the identical 6-call sequence, in the identical order — confirmed by direct comparison of the two call logs, not just by inspection. The Facade doesn't change what happens underneath at all; it changes how much the client has to know and write to make it happen — one call instead of six.

Where This Connects

This chapter's findingWhat it sets up
A verified real AttributeError, fixed by wrapping rather than rewritingChapter 6's Proxy reuses the identical "wrap an object, expose a compatible interface" shape for a genuinely different purpose — controlling access, not fixing incompatibility
Facade's verified identical call sequence, just fewer client-facing callsSoftware Architecture & System Design's own treatment of layered architecture, where a whole layer often plays exactly this simplifying role
Both patterns leaving the wrapped/underlying objects completely unmodifiedChapter 5's Decorator, which also wraps an object without modifying it — but to add behavior, not to translate or simplify it

Hands-On Exercises

Exercise 1

Using this chapter's own PaymentGatewayAdapter as a template, write an adapter for a second legacy gateway whose method is process(total_pence) (British pence, not cents) instead of make_payment. Verify it correctly converts 19.99 dollars into the right number of pence and successfully passes through the same unmodified checkout() function.

📄 View solution
Exercise 2

Using this chapter's own HomeTheaterFacade, add a end_movie() method that turns everything off in the reverse order it was turned on (DVD player first, then projector, then amplifier). Verify the resulting call log matches the expected reversed order.

📄 View solution
Exercise 3

Using this chapter's own two verified demonstrations, explain the specific difference between what Adapter fixes and what Facade fixes — that is, why the payment gateway problem couldn't have been solved with a Facade, and why the home theater problem couldn't have been solved with an Adapter.

📄 View solution

Chapter 4 Quick Reference

  • Adapter: wraps an incompatible object and exposes the interface a client expects, translating each call — verified fixing a real AttributeError and a correct dollars-to-cents conversion (49.99 → 4999)
  • Facade: hides a complex, multi-step subsystem behind one simple method — verified producing the identical 6-call sequence as manual orchestration, just collapsed into one client-facing call
  • Neither pattern modifies the objects it wraps — both add a layer in front, not a change underneath
  • Next chapter: Structural Patterns II — Decorator and Composite