Structural patterns organize relationships between objects. Adapter wraps incompatible interfaces, Decorator adds behavior, and Facade simplifies complex subsystems.
// Adapter — make incompatible interfaces work together
// Old payment interface
class OldPayPal {
makePayment(amount: number, currency: string) {
console.log(\`PayPal: \${amount} \${currency}\`);
}
}
// New interface your app expects
interface PaymentProcessor {
charge(cents: number): void;
}
// Adapter bridges the gap
class PayPalAdapter implements PaymentProcessor {
constructor(private paypal: OldPayPal) {}
charge(cents: number) {
this.paypal.makePayment(cents / 100, 'USD');
}
}
const processor: PaymentProcessor = new PayPalAdapter(new OldPayPal());
processor.charge(4999); // PayPal: 49.99 USD// Decorator — add behavior without subclassing
interface Logger {
log(message: string): void;
}
class ConsoleLogger implements Logger {
log(message: string) { console.log(message); }
}
class TimestampLogger implements Logger {
constructor(private wrapped: Logger) {}
log(message: string) {
this.wrapped.log(\`[\${new Date().toISOString()}] \${message}\`);
}
}
class PrefixLogger implements Logger {
constructor(private wrapped: Logger, private prefix: string) {}
log(message: string) { this.wrapped.log(\`\${this.prefix} \${message}\`); }
}
// Stack decorators
const logger = new PrefixLogger(
new TimestampLogger(new ConsoleLogger()), '[INFO]');
logger.log('Server started');
// [INFO] [2026-04-10T...] Server started// Facade — simplify a complex subsystem
class OrderFacade {
constructor(
private inventory: InventoryService,
private payment: PaymentService,
private shipping: ShippingService,
private email: EmailService,
) {}
async placeOrder(cart: Cart, card: CreditCard, address: Address) {
await this.inventory.reserve(cart.items);
await this.payment.charge(card, cart.total);
const tracking = await this.shipping.dispatch(cart.items, address);
await this.email.sendConfirmation(cart.userId, tracking);
return tracking;
}
}