The sensor library speaks Fahrenheit, your app speaks Celsius, and neither side will change for you: one belongs to a vendor and the other has 40 call sites. Adapter is the Gang of Four structural pattern that lets the interface of an existing class be used as another interface, through a thin object that translates between them, and its other recorded name is simply wrapper. Neither original class gets edited. The adapter stands between them and speaks both languages.
An adapter is really just a translator object: it implements the interface your code expects and forwards every call to the thing that doesn't, converting on the way through. That makes it the rare pattern you've certainly used already, because every time a library hands you the wrong shape and you wrap it, you're here.
Skip the pattern and the translation doesn't disappear, it multiplies. Below, a third-party FahrenheitSensor feeds two functions, each carrying its own pasted copy of the Fahrenheit-to-Celsius formula. The first copy is right. The second lost its parentheses somewhere between editor and commit, and operator precedence did the rest. Trace what log_temperature computes for boiling water before reading the comments:
The broken line evaluates 32 * 5 / 9 first, about 17.8, and subtracts that from 212, logging boiling water as 194.2 degrees Celsius. The Java, C#, C++, and Go tabs are sneakier still: integer division turns the same expression into exactly 17, so they log 195.0. One formula, two languages' worth of distinct wrong answers, and every additional call site is another chance to mint a new one.
The adapter version declares the interface the app actually wants, TemperatureSource with a single celsius() method, and builds exactly one bridge to the sensor. CelsiusAdapter implements the Target, holds the FahrenheitSensor, and owns the formula. Before reading past the code, confirm the prediction the last section set up: what does display_temperature print now, and how many copies of the conversion exist?
It prints 100.0 C, and the formula appears exactly once, inside the adapter's celsius(). The display function takes any TemperatureSource and never learns Fahrenheit exists, the sensor class is untouched vendor code, and the parenthesis bug from the last section now has exactly one line in the codebase where it could live, which is also the line a reviewer will read most carefully.
Three roles, with names that describe the geometry. The Target is the interface the client demands (TemperatureSource). The Adaptee is the existing class with the wrong interface and the right behavior (FahrenheitSensor). The Adapter implements the Target while holding the Adaptee, turning each incoming call into one or more outgoing ones. The client sees only the Target, the adaptee only its own interface, and neither knows the other exists.
The pattern is named after the travel plug adapter, and the mapping is nearly exact: your laptop's plug is the Target shape, the foreign wall socket is the Adaptee, and the adapter presents one face to each. The analogy breaks at the most instructive spot. A travel adapter changes only the shape and passes the electricity through untouched, while software adapters routinely convert the payload too, the way Fahrenheit became Celsius. That conversion logic is the part that can be wrong, which is exactly why the pattern insists it exist in one place.
Installing the pattern is four moves:
celsius(). Deriving it from the adaptee's shape instead produces a wrapper that translates nothing.FahrenheitSensor as a private field, leaving the third-party class byte-for-byte unmodified. Composition is the whole trick: the adaptee never learns it's being translated.(f - 32) * 5 / 9. That formula now exists once, reviewed once, fixed once if it's ever wrong.display_temperature() accepts any TemperatureSource, so the adapter slots in beside native implementations indistinguishably. The client gains a sensor without gaining a dependency on Fahrenheit.Go barely notices this pattern is a pattern. A struct holds the adaptee, implements the target interface, and structural typing handles the rest, no declaration connecting the two. The Go tab above is the entire idiom, and it reads like ordinary Go because it is ordinary Go.
Rust gives the adapter a name of its own: the newtype pattern. The language's orphan rule forbids implementing a foreign trait on a foreign type, so when a library type needs to speak a trait it doesn't know, you wrap it in a one-field tuple struct like CelsiusAdapter(FahrenheitSensor), and the wrapper, being local, can implement anything. The result is the GoF adapter arrived at from a completely different direction, less a style choice than the compiler's required route for after-the-fact interface translation.
You might think adapter and wrapper are interchangeable words, and Wikipedia even lists wrapper as this pattern's alternate name while noting the same alias belongs to Decorator. Wrapper describes the mechanism, holding an object and forwarding to it, which several patterns share. Adapter is the wrapper whose entire point is changing the interface: in goes celsius(), out comes read_fahrenheit(). A wrapper that keeps the same interface and adds behavior is a decorator, and one that keeps the same interface to control access is a proxy. Same shape three times, sorted by intent.
The other classic mix-up is Adapter versus Bridge, and timing settles it. An adapter is retrofitted between two interfaces that already exist and already disagree, which is why it appears when a codebase meets a library. A bridge is drawn up front, splitting one design into two hierarchies that will grow independently. Adapter repairs a mismatch after the fact, and Bridge prevents one from forming, so by the time you're reaching for a wrapper, the bridge moment has usually passed.
The failure mode is the leaky adapter. When the Target promises operations the Adaptee can't honestly perform, the adapter starts faking: returning empty defaults, throwing from half its methods, or silently dropping data the conversion can't carry. Callers get an interface that type-checks and lies. A second smell is stacking, an adapter wrapping an adapter because two teams each wrapped their own boundary, where each layer adds a hop and hides the original behavior a little deeper.
Both smells share a root: the adapter got used as a substitute for agreeing on an interface. The pattern shines when the adaptee is genuinely immovable, vendor code, a standard library, a legacy module nobody may touch. When both sides are yours, changing one of them beats translating between them forever.
The mechanics take five minutes, and these five details decide whether the translation holds:
Two questions before you go. First: the inline version logged boiling water at 194.2 degrees. What property of the adapter version makes that class of bug structurally rarer, not just less likely? Second: Adapter and Facade both stand between your code and someone else's. What single question tells them apart at a glance? Both answers are on this page, the second in Which Wrapper Is This.
Then go spot it in the wild, starting with a line you've probably typed: new BufferedReader(new InputStreamReader(System.in)). Java's InputStreamReader translates byte streams into character streams, a textbook adapter between two interfaces that couldn't talk, and Oracle's own docs charmingly call it a "bridge", proof that even the JDK's authors mix up these names. Last, grep your own codebase for functions named convert or toSomething that are called from more than one place. Each one is a translation living in the open, waiting for the interface that would give it a single home.
class FahrenheitSensor:
# Third-party hardware library: not yours to edit
def read_fahrenheit(self) -> float:
return 212.0
def display_temperature(sensor: FahrenheitSensor) -> str:
# Conversion pasted inline, correctly this time
celsius = (sensor.read_fahrenheit() - 32) * 5 / 9
return f"{celsius:.1f} C"
def log_temperature(sensor: FahrenheitSensor) -> str:
# Same conversion, pasted again, parentheses lost in transit
celsius = sensor.read_fahrenheit() - 32 * 5 / 9
return f"logged {celsius:.1f} C"
# Example usage
sensor = FahrenheitSensor()
print(display_temperature(sensor)) # 100.0 C
print(log_temperature(sensor)) # logged 194.2 C <- boiling water, apparentlyfrom abc import ABC, abstractmethod
class TemperatureSource(ABC): # Target: the interface the app speaks
@abstractmethod
def celsius(self) -> float: ...
class FahrenheitSensor: # Adaptee: third-party, not yours to edit
def read_fahrenheit(self) -> float:
return 212.0
class CelsiusAdapter(TemperatureSource): # Adapter: speaks Target, holds Adaptee
def __init__(self, sensor: FahrenheitSensor):
self._sensor = sensor
def celsius(self) -> float:
# The one and only conversion site in the codebase
return (self._sensor.read_fahrenheit() - 32) * 5 / 9
def display_temperature(source: TemperatureSource) -> str:
return f"{source.celsius():.1f} C"
# Example usage
adapter = CelsiusAdapter(FahrenheitSensor())
print(display_temperature(adapter)) # 100.0 C