Adapter Pattern
Adapter Pattern
Section titled “Adapter Pattern”The Adapter pattern converts the interface of one class into another interface that clients expect. It lets classes work together that otherwise couldn’t because of incompatible interfaces.
Analogy
Section titled “Analogy”A travel power adapter lets a US plug (Type A) work in a UK socket (Type G). The adapter doesn’t change either the plug or the socket — it translates between them.
When to Use
Section titled “When to Use”- Integrating third-party libraries with your existing interfaces
- Using legacy code alongside new code
- Making two independently developed systems work together
C# Example — Payment Gateway Adapter
Section titled “C# Example — Payment Gateway Adapter”You have an interface your system uses, and a third-party SDK with a different interface:
// Your application's payment interfacepublic interface IPaymentGateway{ Task<PaymentResult> ChargeAsync(string customerId, decimal amount, string currency); Task RefundAsync(string transactionId);}
// Third-party Stripe SDK (incompatible interface)public class StripeClient{ public async Task<StripeCharge> CreateChargeAsync(StripeChargeOptions options) { ... } public async Task<StripeRefund> CreateRefundAsync(string chargeId) { ... }}
// Adapter — wraps Stripe and implements your interfacepublic class StripePaymentAdapter : IPaymentGateway{ private readonly StripeClient _stripe;
public StripePaymentAdapter(StripeClient stripe) { _stripe = stripe; }
public async Task<PaymentResult> ChargeAsync(string customerId, decimal amount, string currency) { var options = new StripeChargeOptions { CustomerId = customerId, Amount = (long)(amount * 100), // Stripe uses cents Currency = currency.ToLower(), };
var charge = await _stripe.CreateChargeAsync(options);
return new PaymentResult { TransactionId = charge.Id, Success = charge.Status == "succeeded", Amount = charge.Amount / 100m, }; }
public async Task RefundAsync(string transactionId) { await _stripe.CreateRefundAsync(transactionId); }}
// Registrationservices.AddScoped<IPaymentGateway, StripePaymentAdapter>();
// Usage — business code only knows IPaymentGatewaypublic class OrderService{ private readonly IPaymentGateway _payment;
public OrderService(IPaymentGateway payment) => _payment = payment;
public async Task CompleteOrderAsync(Order order) { var result = await _payment.ChargeAsync(order.CustomerId, order.Total, "GBP"); if (!result.Success) throw new PaymentFailedException(); }}TypeScript Example — Logger Adapter
Section titled “TypeScript Example — Logger Adapter”// Your logging interfaceinterface Logger { info(message: string, meta?: object): void; error(message: string, error?: Error): void; debug(message: string, meta?: object): void;}
// Third-party Winston logger (different interface)import winston from 'winston';
// Adapterclass WinstonAdapter implements Logger { private logger: winston.Logger;
constructor() { this.logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [new winston.transports.Console()], }); }
info(message: string, meta?: object): void { this.logger.info(message, meta); }
error(message: string, error?: Error): void { this.logger.error(message, { error: error?.message, stack: error?.stack }); }
debug(message: string, meta?: object): void { this.logger.debug(message, meta); }}
// Swapping to a different logger (Pino) requires only changing the adapterclass PinoAdapter implements Logger { private logger = pino();
info(message: string, meta?: object) { this.logger.info(meta, message); } error(message: string, error?: Error) { this.logger.error({ err: error }, message); } debug(message: string, meta?: object) { this.logger.debug(meta, message); }}Class Adapter vs Object Adapter
Section titled “Class Adapter vs Object Adapter”Object Adapter (shown above) — wraps an instance via composition. Preferred because it doesn’t require inheritance.
Class Adapter — uses multiple inheritance (where supported) to inherit from both the target and adaptee:
class StripeAdapter extends StripeClient implements IPaymentGateway { // override StripeClient methods to match IPaymentGateway}Object adapter is more flexible — you can adapt different instances, including subclasses.
Benefits
Section titled “Benefits”- Doesn’t modify the existing code (third-party library stays unchanged)
- Single Responsibility: the adapter handles the translation
- Open/Closed: add new adapters without changing existing code
- Easy to swap implementations — change the adapter, not the business logic