🔌 Analogy
You travel abroad with a laptop charger that has the wrong plug shape. You don’t rewire the wall or buy a new laptop — you slot in a travel adapter. It exposes the socket your charger expects on one side and plugs into the foreign socket on the other. Same idea, in code.
The problem
Your client code is written against a Duck (it calls Fly and Quack). You have a perfectly good Turkey, but it speaks a different interface — it Flys and Gobbles. You can’t change Turkey (pretend it’s a third-party package). An Adapter wraps the turkey and presents it as a duck.
Structure
classDiagram
class Duck {
<<interface>>
+Fly()
+Quack()
}
class Turkey {
+Fly()
+Gobble()
}
class TurkeyAdapter {
-turkey Turkey
+Fly()
+Quack()
}
Duck <|.. TurkeyAdapter
TurkeyAdapter o--> Turkey : wraps & translatesTurkeyAdapter satisfies Duck, and inside each method it translates the call to the turkey’s vocabulary — Quack() becomes Gobble().
Idiomatic Go
Because interfaces are implicit, the adapter just needs the right methods. Edit and Run:
package main
import "fmt"
// Target: the interface the client wants.
type Duck interface {
Fly()
Quack()
}
// Adaptee: an existing type with an incompatible interface.
type Turkey struct{}
func (Turkey) Fly() { fmt.Println("flying a short distance") }
func (Turkey) Gobble() { fmt.Println("gobble gobble") }
// Adapter: makes a Turkey usable wherever a Duck is expected.
type TurkeyAdapter struct{ turkey Turkey }
func (a TurkeyAdapter) Quack() { a.turkey.Gobble() } // translate the name
func (a TurkeyAdapter) Fly() {
for i := 0; i < 5; i++ { // turkeys fly short, so flap a few times
a.turkey.Fly()
}
}
func main() {
var duck Duck = TurkeyAdapter{turkey: Turkey{}}
duck.Quack()
duck.Fly()
}
🐹 Adapter vs Decorator
They look alike — both wrap an object — but they answer different questions. Decorator keeps the same interface and adds behavior. Adapter changes the interface so an object fits where it otherwise couldn’t. If you’re translating names/shapes, it’s an adapter; if you’re enriching the same contract, it’s a decorator.
Three wrappers, three intents
Adapter, Decorator, and Proxy all wrap another object — the difference is why:
| Adapter | Decorator | Proxy | |
|---|---|---|---|
| Interface | different from the wrapped type | same as wrapped | same as wrapped |
| Purpose | change the interface | add behavior | control access |
| Adds behavior? | no (just translates) | yes | usually no |
If you’re translating names/shapes so a type fits, it’s an Adapter. If you’re enriching the same contract, it’s a Decorator. If you’re gatekeeping (lazy-load, auth, remote), it’s a Proxy.
In the standard library
strings.NewReader,bytes.NewReader— adapt a string/[]byte toio.Reader.http.HandlerFunc— adapts a function to thehttp.Handlerinterface.sort.Reverse— adapts asort.Interfaceinto one that sorts the other way.
Pitfalls
⚠️ A swarm of adapters is a smell
One or two adapters at a boundary with third-party code is healthy. But if you find yourself adapting everything to everything, your abstractions probably don’t line up — that’s a cue to rethink the interfaces rather than keep translating between them.
When to use it — and when not
✅ Reach for it when
- You want to use an existing or third-party type, but its interface doesn't match what your code expects.
- You can't (or don't want to) change the source type — it's legacy, vendored, or generated.
- You need a stable interface boundary between your code and someone else's.
⛔ Think twice when
- You control the source type and can just make it satisfy the interface directly.
- Lots of adapters pile up — it may signal mismatched abstractions that deserve a redesign.
Related patterns
Attach new behavior to an object by wrapping it in another object of the same interface — without changing the original's type.
structuralBridgeDecouple an abstraction from its implementation so the two can vary independently, instead of multiplying into N×M types.
structuralFacadeProvide a single, simplified interface over a complex subsystem, so clients don't have to orchestrate its parts.
behavioralStrategyDefine a family of interchangeable algorithms, encapsulate each one, and select which to use at runtime.
Check your understanding
Score: 0 / 51. What does an Adapter change?
Adapter translates between interfaces. The adaptee's methods (Gobble, Fly) are exposed through the target interface the client wants (Quack, Fly).
2. Why are adapters often trivial in Go?
No `implements` keyword: a wrapper that has Fly() and Quack() simply *is* a Duck. Even a function can be adapted (http.HandlerFunc).
3. Which standard-library type is a function Adapter?
http.HandlerFunc adapts a plain func(w, r) into the http.Handler interface by giving it a ServeHTTP method that just calls the function.
4. GoF describes 'object' and 'class' adapters. Which does Go use?
A class adapter multiply-inherits from target and adaptee — impossible in Go. The object adapter composes (holds the adaptee and forwards calls), which is the idiomatic Go form. Embedding can shortcut the forwarding when names already line up.
5. Adapter vs Facade — what's the distinction?
Adapter is about making a specific incompatible type fit an interface the client already expects. Facade is about giving a simpler front door to many types/calls. Adapter targets one interface; Facade wraps many.
Comments
Sign in with GitHub to join the discussion.