Adapter
Adapter is a structural pattern that wraps an object with a mismatched interface behind a translator, letting client code call it as if it spoke the expected language all along.
Problem
A payment module in your checkout flow was built around a `charge(amountCents, cardToken)` method, and every screen in the app calls it that way. Now the business wants to add a new payment provider whose SDK only exposes `submitTransaction({ total, currency, source })`.
You can't rename the SDK's method - it's someone else's package, updated independently. Rewriting your entire checkout flow to match the new SDK's shape would touch dozens of call sites for the sake of one provider. And the next provider you integrate will likely have yet another shape, repeating the problem.
Solution
Introduce a thin wrapper class that implements the interface your code already expects, and internally translates each call into whatever the external object actually needs. The wrapper receives calls in the familiar shape, reformats the arguments, invokes the wrapped object, and reshapes the result back if needed.
Neither side has to change: the caller keeps using the interface it always used, and the wrapped object keeps its own interface untouched. All the translation logic - renaming fields, converting units, reordering parameters - lives in one isolated place instead of being smeared across the codebase.
When to Use
- You need an existing class to work with your code, but its method names, argument order, or data format don't line up with what your code expects.
- You're integrating a third-party SDK or legacy module you cannot edit, and don't want its interface leaking into the rest of the application.
- Several related classes provide similar functionality through slightly different interfaces, and you want to unify them under one shape.
Real-World Examples
- **Travel power plug adapters** - let a device built for one country's socket shape connect to a different country's outlet without altering the device.
- **Java's `Arrays.asList()`** - wraps a plain array so it can be handed to any code expecting the `List` interface, without copying the underlying data.
- **Payment gateway SDK wrappers** - normalize Stripe's, PayPal's, and Adyen's differently-shaped charge APIs behind one internal `PaymentProvider` interface.
Structure
Client code is written against the Target interface only. The Adapter class implements Target, but instead of doing the work itself, it holds an Adaptee instance and translates every incoming call into the shape Adaptee actually understands.
Participants
Drives the application logic and calls objects strictly through the Target interface, unaware of any adapting happening behind it.
In the diagram: Client.
The interface the Client already relies on - the contract that any object handed to the Client must satisfy.
In the diagram: Target, implemented as PaymentProcessor.
The existing class with the useful behavior - often a third-party SDK or legacy module - whose method signatures don't match Target.
In the diagram: Adaptee, implemented as NewProviderSDK.
Sits between the two: implements Target so the Client can call it normally, and internally reformats each call before forwarding it to the wrapped Adaptee.
In the diagram: Adapter, implemented as PaymentAdapter.
class PaymentProcessor {
charge(amountCents, cardToken) {
throw new Error('charge() must be implemented');
}
}
class NewProviderSDK {
submitTransaction({ total, currency, source }) {
console.log(`NewProviderSDK: charged ${total / 100} ${currency} via ${source}`);
return { status: 'ok', reference: 'txn_8842' };
}
}
class PaymentAdapter extends PaymentProcessor {
#sdk;
constructor(sdk) {
super();
this.#sdk = sdk;
}
charge(amountCents, cardToken) {
const result = this.#sdk.submitTransaction({
total: amountCents,
currency: 'USD',
source: cardToken,
});
return result.status === 'ok';
}
}
function checkout(processor, amountCents, cardToken) {
const success = processor.charge(amountCents, cardToken);
console.log(success ? 'Payment accepted' : 'Payment failed');
}
const adapter = new PaymentAdapter(new NewProviderSDK());
checkout(adapter, 2599, 'tok_visa_4242');
Step 1 of 6
PaymentProcessor is the target interface
`charge()` just throws - this is the contract every payment processor the client code talks to must implement.
Step 2 of 6
NewProviderSDK is the incompatible existing class
Its method is called `submitTransaction()`, not `charge()`, and it takes one options object (`{ total, currency, source }`) instead of two separate arguments - it doesn't match `PaymentProcessor` at all.
Step 3 of 6
PaymentAdapter extends the target and wraps the SDK
`PaymentAdapter` `extends PaymentProcessor` so it satisfies the target interface, and its constructor stores the incompatible `sdk` in the private field `#sdk`.
Step 4 of 6
charge() translates the call into submitTransaction's shape
It builds the `{ total, currency, source }` object `submitTransaction` expects from `amountCents`/`cardToken`, then converts the result back into the boolean `charge()` promises to return.
Step 5 of 6
checkout() only knows PaymentProcessor
It calls `processor.charge(amountCents, cardToken)` - nothing here mentions `NewProviderSDK`, so `checkout` works with any `PaymentProcessor` implementation.
Step 6 of 6
The client plugs the SDK in through the adapter
`new PaymentAdapter(new NewProviderSDK())` is passed straight into `checkout` - the incompatible SDK now fits without `checkout` or `NewProviderSDK` changing at all.
interface PaymentProcessor {
charge(amountCents: number, cardToken: string): boolean;
}
interface SubmitTransactionParams {
total: number;
currency: string;
source: string;
}
class NewProviderSDK {
public submitTransaction({ total, currency, source }: SubmitTransactionParams): { status: string; reference: string } {
console.log(`NewProviderSDK: charged ${total / 100} ${currency} via ${source}`);
return { status: 'ok', reference: 'txn_8842' };
}
}
class PaymentAdapter implements PaymentProcessor {
private sdk: NewProviderSDK;
constructor(sdk: NewProviderSDK) {
this.sdk = sdk;
}
public charge(amountCents: number, cardToken: string): boolean {
const result = this.sdk.submitTransaction({
total: amountCents,
currency: 'USD',
source: cardToken,
});
return result.status === 'ok';
}
}
function checkout(processor: PaymentProcessor, amountCents: number, cardToken: string): void {
const success = processor.charge(amountCents, cardToken);
console.log(success ? 'Payment accepted' : 'Payment failed');
}
const adapter = new PaymentAdapter(new NewProviderSDK());
checkout(adapter, 2599, 'tok_visa_4242');
interface PaymentProcessor {
boolean charge(int amountCents, String cardToken);
}
class NewProviderSDK {
public String submitTransaction(int total, String currency, String source) {
System.out.println("NewProviderSDK: charged " + (total / 100.0) + " " + currency + " via " + source);
return "ok";
}
}
class PaymentAdapter implements PaymentProcessor {
private final NewProviderSDK sdk;
PaymentAdapter(NewProviderSDK sdk) {
this.sdk = sdk;
}
public boolean charge(int amountCents, String cardToken) {
String status = sdk.submitTransaction(amountCents, "USD", cardToken);
return status.equals("ok");
}
}
static void checkout(PaymentProcessor processor, int amountCents, String cardToken) {
boolean success = processor.charge(amountCents, cardToken);
System.out.println(success ? "Payment accepted" : "Payment failed");
}
checkout(new PaymentAdapter(new NewProviderSDK()), 2599, "tok_visa_4242");
interface IPaymentProcessor
{
bool Charge(int amountCents, string cardToken);
}
class NewProviderSdk
{
public string SubmitTransaction(int total, string currency, string source)
{
Console.WriteLine($"NewProviderSDK: charged {total / 100.0} {currency} via {source}");
return "ok";
}
}
class PaymentAdapter : IPaymentProcessor
{
private readonly NewProviderSdk _sdk;
public PaymentAdapter(NewProviderSdk sdk) => _sdk = sdk;
public bool Charge(int amountCents, string cardToken)
{
string status = _sdk.SubmitTransaction(amountCents, "USD", cardToken);
return status == "ok";
}
}
static void Checkout(IPaymentProcessor processor, int amountCents, string cardToken) =>
Console.WriteLine(processor.Charge(amountCents, cardToken) ? "Payment accepted" : "Payment failed");
Checkout(new PaymentAdapter(new NewProviderSdk()), 2599, "tok_visa_4242");
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
@abstractmethod
def charge(self, amount_cents: int, card_token: str) -> bool: ...
class NewProviderSDK:
def submit_transaction(self, total: int, currency: str, source: str) -> str:
print(f"NewProviderSDK: charged {total / 100} {currency} via {source}")
return "ok"
class PaymentAdapter(PaymentProcessor):
def __init__(self, sdk: NewProviderSDK) -> None:
self._sdk = sdk
def charge(self, amount_cents: int, card_token: str) -> bool:
status = self._sdk.submit_transaction(
total=amount_cents, currency="USD", source=card_token
)
return status == "ok"
def checkout(processor: PaymentProcessor, amount_cents: int, card_token: str) -> None:
success = processor.charge(amount_cents, card_token)
print("Payment accepted" if success else "Payment failed")
adapter = PaymentAdapter(NewProviderSDK())
checkout(adapter, 2599, "tok_visa_4242")
Step 1 of 6
PaymentProcessor is the target interface
`charge()` is declared `@abstractmethod` - this is the contract every processor the client depends on must fulfil.
Step 2 of 6
NewProviderSDK is the incompatible existing class
Its method `submit_transaction()` takes keyword arguments `total`/`currency`/`source` and returns a plain string status, not the `charge(amount_cents, card_token) -> bool` shape `PaymentProcessor` needs.
Step 3 of 6
PaymentAdapter subclasses the target and stores the SDK
`PaymentAdapter(PaymentProcessor)` satisfies the abstract base, and `__init__` stores the wrapped `sdk` on `self._sdk`.
Step 4 of 6
charge() adapts arguments and return value
It calls `self._sdk.submit_transaction(total=..., currency="USD", source=...)` and turns the returned string back into the `bool` that `charge()`'s callers expect via `status == "ok"`.
Step 5 of 6
checkout() depends only on PaymentProcessor
Its parameter is typed `processor: PaymentProcessor` and it only calls `processor.charge(...)` - `NewProviderSDK` never appears here.
Step 6 of 6
The client wires the SDK in through the adapter
`PaymentAdapter(NewProviderSDK())` is created and passed straight into `checkout` - the incompatible SDK works without touching `checkout` or `NewProviderSDK`.
Click "Run" to execute this code in a sandboxed frame and see console output here.
TypeScript runs as plain JavaScript here - type annotations are stripped, not type-checked.
Click "Run" to execute this code in a sandboxed frame and see console output here.
Advantages
- Keeps translation logic out of your business code - each adapter is a small, focused unit that only knows how to speak to one external interface.
- New external systems get their own adapter without touching the Client or any adapter already in production.
- Lets you plug in third-party or legacy code you can't modify, without contaminating your codebase with its quirks.
Disadvantages
- Adds an extra class and an extra hop for every adaptation - for a one-off integration, editing the original class (if you can) may be simpler.
- Stacking several adapters on top of one another obscures where a value actually gets transformed, complicating debugging.
- An adapter can only translate what the underlying interface exposes - it can't invent capabilities the Adaptee doesn't have.
Question 1 of 10
Where does the translation logic (renaming fields, converting units, reordering parameters) live in the solution?
Correct! All the translation logic lives in one isolated wrapper class rather than being smeared across the codebase.
Not quite. All the translation logic lives in one isolated wrapper class rather than being smeared across the codebase.
Question 2 of 10
What is a stated limitation of Adapter?
Correct! An adapter can only translate what the underlying interface exposes; it can't invent capabilities the Adaptee doesn't have.
Not quite. An adapter can only translate what the underlying interface exposes; it can't invent capabilities the Adaptee doesn't have.
Question 3 of 10
According to the pattern's participants, what role does the Adapter class play?
Correct! The Adapter implements Target so the Client can call it normally, while internally holding the wrapped Adaptee and reformatting each call before forwarding it.
Not quite. The Adapter implements Target so the Client can call it normally, while internally holding the wrapped Adaptee and reformatting each call before forwarding it.
Question 4 of 10
According to the real-world examples, what does Java's `Arrays.asList()` do?
Correct! Arrays.asList() wraps a plain array so it can be handed to any code expecting the List interface, without copying the underlying data - a textbook Adapter use.
Not quite. Arrays.asList() wraps a plain array so it can be handed to any code expecting the List interface, without copying the underlying data - a textbook Adapter use.
Question 5 of 10
In the code example, what does the `checkout()` function's signature reveal about its dependency on NewProviderSDK?
Correct! checkout() only calls `processor.charge(amountCents, cardToken)` and nothing in it mentions NewProviderSDK, so it works with any PaymentProcessor implementation.
Not quite. checkout() only calls `processor.charge(amountCents, cardToken)` and nothing in it mentions NewProviderSDK, so it works with any PaymentProcessor implementation.
Question 6 of 10
In the walkthrough, what does PaymentAdapter's `charge()` method do with the SDK's response?
Correct! The adapter builds the request object submitTransaction expects, then converts its result back into the boolean that charge() promises to return.
Not quite. The adapter builds the request object submitTransaction expects, then converts its result back into the boolean that charge() promises to return.
Question 7 of 10
What downside comes from stacking several adapters on top of one another?
Correct! Stacking several adapters obscures where a value actually gets transformed, complicating debugging.
Not quite. Stacking several adapters obscures where a value actually gets transformed, complicating debugging.
Question 8 of 10
In the Adapter structure, what interface does Client code depend on?
Correct! Client code is written against the Target interface only, unaware that an Adapter is translating calls behind it.
Not quite. Client code is written against the Target interface only, unaware that an Adapter is translating calls behind it.
Question 9 of 10
In the checkout example, what's the actual problem with the new provider's SDK?
Correct! Your code calls `charge(amountCents, cardToken)` everywhere, but the new SDK only exposes `submitTransaction({ total, currency, source })` - a mismatched method name and argument shape.
Not quite. Your code calls `charge(amountCents, cardToken)` everywhere, but the new SDK only exposes `submitTransaction({ total, currency, source })` - a mismatched method name and argument shape.
Question 10 of 10
What does Adapter fundamentally do?
Correct! Adapter wraps a mismatched interface behind a translator so client code can call it as if it always spoke the expected language.
Not quite. Adapter wraps a mismatched interface behind a translator so client code can call it as if it always spoke the expected language.