Skip to content

Adapter

Adapter converts the interface of an existing object into the interface a client expects, letting classes cooperate that otherwise could not because their method shapes do not line up.

Your code is written against a clean interface you control. Then you need to plug in a third-party library, a legacy module, or some external API that does the right job but exposes the wrong shape: different method names, different argument order, data in a format you do not want. You cannot change the foreign code, and rewriting your whole codebase to match it would be absurd.

Adapter is the thin translation layer in between. It implements the interface your client expects and, behind that face, forwards each call to the foreign object, converting names, arguments, and return values as needed. The client keeps talking to the interface it already knows; the adapter absorbs the mismatch. You can swap one foreign service for another by writing a new adapter, leaving everything upstream untouched.

classDiagram
  class Target {
    <<interface>>
    +fetch(city) Weather
  }
  class Client
  class Adapter {
    -service: LegacyWeatherAPI
    +fetch(city) Weather
  }
  class LegacyWeatherAPI {
    +getTempByZip(zip) number
  }
  Target <|.. Adapter
  Adapter --> LegacyWeatherAPI : delegates to
  Client --> Target : uses
An adapter implements the target interface and delegates to the foreign service
  • Target — the interface your client code is written against and wants to call.
  • Adaptee (LegacyWeatherAPI) — the existing object with a useful capability but an incompatible interface.
  • Adapter — implements the target interface and translates each call into one or more calls on the adaptee.
  • Client — depends only on the target interface, oblivious to the adaptee behind it.

Your app expects a WeatherProvider that takes a city name and returns degrees Celsius. The third-party service only takes a postal code and returns Fahrenheit. The adapter bridges the gap.

// Target interface our app is written against.
interface WeatherProvider {
currentCelsius(city: string): number;
}
// The foreign service we cannot change.
class LegacyWeatherApi {
tempFahrenheitByZip(zip: string): number {
return 68; // pretend network call
}
}
const cityToZip: Record<string, string> = { London: 'EC1A', Tokyo: '100' };
class LegacyWeatherAdapter implements WeatherProvider {
constructor(private readonly service: LegacyWeatherApi) {}
currentCelsius(city: string): number {
const zip = cityToZip[city] ?? '00000';
const f = this.service.tempFahrenheitByZip(zip);
return Math.round(((f - 32) * 5) / 9);
}
}
const provider: WeatherProvider = new LegacyWeatherAdapter(new LegacyWeatherApi());
console.log(provider.currentCelsius('London')); // 20
  • Pro: lets you reuse existing or third-party code whose interface you cannot change.
  • Pro: isolates the conversion in one place, so a service swap means writing one new adapter.
  • Pro: keeps clients depending only on the clean target interface, improving testability.
  • Con: adds a layer of indirection and an extra class for every adaptee you wrap.
  • Con: complex conversions hide real work inside the adapter, which can mask performance or semantic mismatches.
  • Bridge looks similar but is designed up front to let abstraction and implementation vary, whereas Adapter retrofits two existing interfaces after the fact.
  • Decorator also wraps an object, but it keeps the same interface and adds behaviour, while Adapter changes the interface.
  • Facade wraps a whole subsystem behind a new simple interface, where Adapter typically wraps a single object to match an existing one.
What does an Adapter primarily change about an object?
In the Adapter pattern, what is the "adaptee"?
How does Adapter differ from Decorator?
Why is the Adapter pattern useful with third-party code?