COMP 305: Object-Oriented Software Design

University of San Diego, Fall 2025

Adapter Pattern

The Adapter pattern (also known as Wrapper) allows objects with incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces by wrapping one interface to match another.

Key Concepts

Interactive Demo

See how adapters allow different payment gateways to work with the same checkout system:

Select Payment Method:

Transaction Log

// Select a payment method and click Process Payment

Implementation Examples

Basic User API Adapter

// Incompatible Legacy API
class LegacyUserAPI {
    getUserInfo(userId) {
        return {
            user_id: userId,
            first_name: 'John',
            last_name: 'Doe',
            email_address: 'john.doe@example.com',
            registration_date: '2023-01-15'
        };
    }
}

// Modern Application expects different interface
class ModernUserDisplay {
    render(user) {
        // Expects: { id, fullName, email, joinDate }
        return `
            <div>
                <h3>${user.fullName}</h3>
                <p>ID: ${user.id}</p>
                <p>Email: ${user.email}</p>
                <p>Joined: ${user.joinDate}</p>
            </div>
        `;
    }
}

// Adapter bridges the gap
class UserAPIAdapter {
    constructor(legacyAPI) {
        this.legacyAPI = legacyAPI;
    }

    getUser(userId) {
        const legacyData = this.legacyAPI.getUserInfo(userId);

        // Transform legacy format to modern format
        return {
            id: legacyData.user_id,
            fullName: `${legacyData.first_name} ${legacyData.last_name}`,
            email: legacyData.email_address,
            joinDate: new Date(legacyData.registration_date).toLocaleDateString()
        };
    }
}

// Usage
const legacyAPI = new LegacyUserAPI();
const adapter = new UserAPIAdapter(legacyAPI);
const display = new ModernUserDisplay();

const user = adapter.getUser(123);
display.render(user); // Works perfectly!

Payment Gateway Adapters

// Different Payment Gateways with Different Interfaces
class PayPalGateway {
    sendPayment(amount, email) {
        return {
            status: 'success',
            transaction_id: 'PP-' + Math.random().toString(36).substr(2, 9),
            amount: amount,
            payee: email
        };
    }
}

class StripeGateway {
    charge(cents, token) {
        return {
            success: true,
            id: 'ch_' + Math.random().toString(36).substr(2, 9),
            amount_charged: cents,
            payment_method: token
        };
    }
}

// Target interface our app expects
class PaymentAdapter {
    processPayment(amount, paymentDetails) {
        throw new Error('Must be implemented by subclass');
    }
}

// Adapter for PayPal
class PayPalAdapter extends PaymentAdapter {
    constructor() {
        super();
        this.gateway = new PayPalGateway();
    }

    processPayment(amount, paymentDetails) {
        const result = this.gateway.sendPayment(amount, paymentDetails.email);
        return {
            success: result.status === 'success',
            transactionId: result.transaction_id,
            amount: result.amount
        };
    }
}

// Adapter for Stripe
class StripeAdapter extends PaymentAdapter {
    constructor() {
        super();
        this.gateway = new StripeGateway();
    }

    processPayment(amount, paymentDetails) {
        const cents = amount * 100;
        const result = this.gateway.charge(cents, paymentDetails.token);
        return {
            success: result.success,
            transactionId: result.id,
            amount: result.amount_charged / 100
        };
    }
}

// Now we can use either payment method with the same interface
function checkout(adapter, amount, details) {
    const result = adapter.processPayment(amount, details);
    console.log(`Payment ${result.success ? 'succeeded' : 'failed'}`);
    return result;
}

Data Format Adapter (XML to JSON)

// Adapting different data formats
class XMLDataService {
    fetchData() {
        return `
            <users>
                <user id="1">
                    <name>Alice</name>
                    <role>Admin</role>
                </user>
                <user id="2">
                    <name>Bob</name>
                    <role>User</role>
                </user>
            </users>
        `;
    }
}

class JSONAdapter {
    constructor(xmlService) {
        this.xmlService = xmlService;
    }

    getData() {
        const xmlString = this.xmlService.fetchData();
        // Simple XML to JSON conversion (in real app, use DOMParser)
        const parser = new DOMParser();
        const doc = parser.parseFromString(xmlString, 'text/xml');
        const users = Array.from(doc.querySelectorAll('user'));

        return users.map(user => ({
            id: user.getAttribute('id'),
            name: user.querySelector('name').textContent,
            role: user.querySelector('role').textContent
        }));
    }
}

Common Use Cases

Benefits

Trade-offs

Adapter vs Facade vs Decorator