Skip to main content

Adapter

complexitypopularity

Let two objects with incompatible interfaces collaborate by putting a translator between them.

The problem

You are building a stock market dashboard. Your app downloads XML feeds and draws handsome charts. Then you find a third-party analytics library that would make the product genuinely smarter, and it turns out to speak only JSON.

Two bad options present themselves. Rewrite the library to accept XML - assuming you even have the source, and assuming you enjoy breaking everyone else who depends on it. Or scatter conversion code through your own classes at every call site, which works right up until the second incompatible library arrives.

The same standoff in miniature: a round hole and a square peg.

The solution

Add a third object whose entire job is translation. The adapter implements the interface the client already calls, holds a reference to the awkward service, and converts on the way through: format, units, argument order, whatever the mismatch happens to be. The wrapped object never learns it has been wrapped.

Three moves, in order: the adapter takes on an interface the existing code can call; the existing code calls the adapter as if nothing unusual is happening; the adapter forwards the request to the service in the shape the service wants. Ambitious versions translate both directions.

Client codeRoundHoleSquarePegAdapterSquarePegnew SquarePegAdapter(sqpeg)1fits(adapter)2getRadius()3getWidth()4width * sqrt(2) / 25return radius6true / false7
  1. 1The client wraps the foreign object once, at the boundary. Everything downstream stays innocent.
  2. 2The hole is handed something that type-checks as a RoundPeg. It has no idea a square is hiding inside.
  3. 3The hole calls the only method it knows, exactly as it would on a real round peg.
  4. 4The adapter turns around and speaks the service language: widths, not radii.
  5. 5The translation happens here and nowhere else - the radius of the smallest circle the square would fit into.
  6. 6A perfectly ordinary radius comes back. The conversion cost the client zero lines.
  7. 7The hole compares numbers and answers. Both sides remain blissfully unaware of each other.

Structure

The object adapter above uses composition - it wraps the service - and works in every language. There is also a class adapter that inherits from both sides at once and overrides the mismatched methods, which requires multiple inheritance and is therefore mostly a C++ story.

RoundHoleradiusgetRadius()fits(peg: RoundPeg)CLIENTRoundPeggetRadius()CLIENTSquarePegwidthgetWidth()INCOMPATIBLESquarePegAdapterpeg: SquarePeggetRadius()ADAPTER
extendsuses

Code

Square pegs, round holes, and the small liar that reconciles them.

// The hole speaks radii. The square peg speaks widths. Nobody is wrong,
// and yet nothing works.
class RoundHole is
method fits(peg: RoundPeg) is
return this.getRadius() >= peg.getRadius()
 
hole = new RoundHole(5)
sqpeg = new SquarePeg(5)
hole.fits(sqpeg) // does not even compile: SquarePeg is not a RoundPeg
 
// So the conversion gets smeared into the client instead:
method fitsAnything(thing) is
if (thing is RoundPeg) then
return this.getRadius() >= thing.getRadius()
if (thing is SquarePeg) then
return this.getRadius() >= thing.getWidth() * Math.sqrt(2) / 2
// ...one more branch for every peg shape anyone ever invents.
// The hole keeps its one clean method. No type switch, no geometry.
class RoundHole is
method fits(peg: RoundPeg) is
return this.getRadius() >= peg.getRadius()
 
// The translation lives in exactly one class:
class SquarePegAdapter extends RoundPeg is
private field peg: SquarePeg
 
method getRadius() is
return peg.getWidth() * Math.sqrt(2) / 2
 
// Client code wraps once at the boundary and forgets about it:
hole.fits(new SquarePegAdapter(sqpeg)) // true
// A hexagonal peg next quarter? New adapter, zero edits here.
// Two classes that already understand each other perfectly.
class RoundHole is
constructor RoundHole(radius) { ... }
 
method getRadius() is
// The radius of the hole.
 
method fits(peg: RoundPeg) is
return this.getRadius() >= peg.getRadius()
 
class RoundPeg is
constructor RoundPeg(radius) { ... }
 
method getRadius() is
// The radius of the peg.
 
// And the outsider: same idea, incompatible vocabulary.
class SquarePeg is
constructor SquarePeg(width) { ... }
 
method getWidth() is
// The width of the square peg.
 
// The adapter poses as a round peg and hides a square one inside.
class SquarePegAdapter extends RoundPeg is
private field peg: SquarePeg
 
constructor SquarePegAdapter(peg: SquarePeg) is
this.peg = peg
 
method getRadius() is
// Report the radius of the smallest circle the square
// would fit inside. That is the whole translation.
return peg.getWidth() * Math.sqrt(2) / 2
 
// Somewhere in client code.
hole = new RoundHole(5)
rpeg = new RoundPeg(5)
hole.fits(rpeg) // true
 
small_sqpeg = new SquarePeg(5)
large_sqpeg = new SquarePeg(10)
hole.fits(small_sqpeg) // will not compile: incompatible types
 
small_adapter = new SquarePegAdapter(small_sqpeg)
large_adapter = new SquarePegAdapter(large_sqpeg)
hole.fits(small_adapter) // true
hole.fits(large_adapter) // false

When to use it

  • You want to use an existing class whose interface does not match the rest of your code, and changing that class is impossible or unwise - the classic legacy or third-party case.
  • You want several existing subclasses to gain a shared capability that cannot go into their superclass. Rather than duplicating it into every child, put it in an adapter and wrap the objects that need it.

Pitfalls

  • Layer for layer's sake. Every adapter is one more interface and one more class. If you control the service, editing it is often the smaller change.
  • The adapter that grows a brain. Translation only. The moment business rules move in, you have built a second implementation nobody knows about.
  • Adapters all the way down. They do not compose the way decorators do. A chain of adapters is usually a sign that two subsystems need a real boundary, not more tape.

Don't confuse it with

  • Decorator. A decorator keeps the interface intact and adds behavior behind it, and because its input and output types match, decorators stack. An adapter changes the interface on purpose, which is exactly why it does not stack.
  • Proxy. A proxy also presents the same interface as its service, but its motive is control: lazy loading, caching, permissions. An adapter has no opinion about access, only about vocabulary.
  • Facade. A facade builds a new, deliberately simpler interface in front of an entire subsystem. An adapter usually wraps a single object and is trying to make an existing interface usable rather than pleasant.
  • Bridge. Bridge is drawn on the whiteboard before either hierarchy exists so both can evolve freely. Adapter shows up later, with a screwdriver, once two things that already exist refuse to connect.

Check yourself

Question 1 of 5

Which interface does an object adapter implement?