Adapter
Wrap an incompatible class so it fits the interface your code already expects — no edits on either side.
Big idea
An Adapter is a thin wrapper that translates one interface into another. Your code keeps calling the interface it was designed around; the adapter converts each call into whatever the foreign class understands — neither side is modified.
The problem it solves
Your dashboard is built around a Thermometer interface with celsius(). You buy a third-party weather station whose SDK has get_reading() returning {"temp_f": 71.6, "ts": …}. You can't edit the SDK, and you don't want if isinstance(sensor, VendorStation) with Fahrenheit conversions sprinkled through the dashboard. Next year a second vendor arrives with yet another shape.
Think of it like
A travel plug adapter. Your laptop charger has a UK plug; the hotel wall has a European socket. You don't rewire the hotel or cut the plug off your charger — a small block in between makes each side see what it expects.
Structure
- Target (
Thermometer) — the interface your client code already uses. - Adaptee (
VendorStation) — the useful class with the wrong interface. - Adapter — implements the target, holds an adaptee, translates.
In code
class LabProbe: # already speaks our interface
def __init__(self, c): self._c = c
def celsius(self): return self._c
class VendorStation: # third-party: can't edit
def get_reading(self):
return {"temp_f": 71.6, "unit": "F"}
class VendorAdapter: # implements Thermometer, wraps the vendor
def __init__(self, station):
self._station = station
def celsius(self):
f = self._station.get_reading()["temp_f"]
return round((f - 32) * 5 / 9, 1)
def show(sensors): # the client: unchanged
for name, s in sensors.items():
print(f"{name:8} {s.celsius():5.1f} C")
show({"lab": LabProbe(21.5), "roof": VendorAdapter(VendorStation())})output
lab 21.5 C
roof 22.0 CRun and edit it in the app.
The dashboard never learns that Fahrenheit exists. When a second vendor arrives, it gets its own adapter; the dashboard still doesn't change.
Watch it
Object adapter vs class adapter
- Object adapter (above) — holds the adaptee; works with any subclass of it; the usual choice.
- Class adapter — inherits from both target and adaptee (multiple inheritance). Less flexible, rarely worth it.
- In Python, a function can be an adapter too:
sorted(items, key=adapt)adapts items to "something comparable".
What does an Adapter change?
When to use it — and when not to
Use it to integrate third-party or legacy code you can't or shouldn't edit, to put several vendors behind one interface, or to keep a foreign API from leaking into your domain (an anti-corruption layer). If you own both sides and they're young, just change one of them — an adapter between two of your own fresh classes is a sign the interfaces should agree.
Pros and cons
| Pros | Cons |
|---|---|
| Reuse code with a mismatched interface | One more class per adaptee |
| Client and adaptee stay untouched (Open/Closed) | Translation can hide costs (a cheap-looking call that's a network round trip) |
| Isolates vendor quirks in one place | Mismatched semantics can't always be adapted cleanly |
How it relates
- Decorator keeps the interface and adds behaviour; Adapter changes the interface and keeps behaviour.
- Facade defines a new, simpler interface over many classes; Adapter makes one class fit an existing interface.
- Proxy keeps exactly the same interface and controls access.
- Bridge is designed up front to separate interface from implementation; Adapter is usually retrofitted.
You wrap a payment SDK's six classes behind one simple pay(amount) method your app defines. Adapter or Facade?
Key terms
- Adapter — A wrapper that converts one interface into another that clients expect.
- Target — The interface the client code is written against.
- Adaptee — The existing class with a useful behaviour but an incompatible interface.
- Anti-corruption layer — Adapters at a system boundary that keep a foreign model from leaking into yours.