Adapter Pattern
Keep an application contract while adapting an existing API, including the meaning of its values.
· Last updated: 2026-10-24 · 5 min read
A room monitor expects Celsius, but an older sensor returns Fahrenheit. Both return a number, so passing 86 straight through would produce a very wrong temperature display. The caller needs the value in the unit it understands.
Keep the conversion at one boundary
If the screen, logger and alarm all call the old sensor directly, each needs to remember the Fahrenheit conversion. A change to the sensor API then reaches all three callers. Put the conversion in one object and let the application ask that object for Celsius.
An adapter makes an existing interface usable through the interface a caller expects. The four authors of Design Patterns, known as the GoF, classify it as a structural pattern.1
Here, an interface is the calling agreement: ICelsiusThermometer offers ReadCelsius(), whose result is in Celsius. FahrenheitAdapter accepts that agreement on one side and calls LegacyThermometer.ReadFahrenheit() on the other. It adapts the unit as well as the method name.
The relationship
RoomMonitor holds an ICelsiusThermometer, so it does not need to know the older API. The adapter holds a reference to that older sensor. The dashed line with a hollow triangle says that the adapter implements the interface.
A small executable example
using System;
using System.Globalization;
var legacy = new LegacyThermometer(86m);
ICelsiusThermometer sensor = new FahrenheitAdapter(legacy);
new RoomMonitor(sensor).Print();
public interface ICelsiusThermometer
{
decimal ReadCelsius();
}
public sealed class LegacyThermometer
{
private readonly decimal fahrenheit;
public LegacyThermometer(decimal fahrenheit) => this.fahrenheit = fahrenheit;
public decimal ReadFahrenheit() => fahrenheit;
}
public sealed class FahrenheitAdapter : ICelsiusThermometer
{
private readonly LegacyThermometer legacy;
public FahrenheitAdapter(LegacyThermometer legacy) => this.legacy = legacy;
public decimal ReadCelsius() => (legacy.ReadFahrenheit() - 32m) * 5m / 9m;
}
public sealed class RoomMonitor
{
private readonly ICelsiusThermometer sensor;
public RoomMonitor(ICelsiusThermometer sensor) => this.sensor = sensor;
public void Print()
{
string value = sensor.ReadCelsius().ToString("F1", CultureInfo.InvariantCulture);
Console.WriteLine($"Room: {value} Celsius");
}
}Room: 30.0 CelsiusThe call goes from RoomMonitor.Print() to ReadCelsius(), then to the old ReadFahrenheit() method. The adapter converts 86 °F to 30 °C with (F − 32) × 5 / 9. Decimal literals such as 5m keep the arithmetic in decimal rather than integer division. The old sensor class is a small stand-in that returns a fixed reading; the example does not contact hardware.
What belongs in the adapter
Use this boundary for differences the caller should not have to understand: units, argument order, return shapes, or the method names in a vendor library (SDK). If an external call can fail, translate that failure into the application’s existing error contract here. Returning a guessed temperature would give the caller a fabricated reading.
A direct conversion function is enough when one caller needs a single calculation. The adapter object is useful when several callers should share a stable contract or when the external component needs to be replaceable.
Adapter and neighboring patterns
Adapter reconciles different calling agreements. Decorator keeps the same agreement and adds behavior around a call. A facade offers a simpler entry to a subsystem; it does not necessarily make an incompatible component satisfy an existing interface.
Related reading
The complete example runs as a .NET 10 console program.
Footnotes
- Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides. Design Patterns. Adapter excerpt. ↩