Skip to content
intermediatePhase 49 · Low-Level Design

Adapter Pattern

Convert one interface to another for compatibility.

30m
0 problems
Topic Progress0%

Object Adapter

Object Adapter uses composition to wrap the adaptee and translate interface calls.

Structure

┌──────────────────────────┐
│     Target (interface)   │
├──────────────────────────┤
│ + request(): Result      │
└──────────┬───────────────┘
           │ implemented by
┌──────────┴───────────────┐
│       Adapter            │
├──────────────────────────┤
│ - adaptee: Adaptee       │  ← composition
├──────────────────────────┤
│ + request(): Result      │  ← translates
└──────────┬───────────────┘
           │ wraps
┌──────────┴───────────────┐
│     Adaptee (class)      │
├──────────────────────────┤
│ + specificRequest(): void│
└──────────────────────────┘

Implementation

// Target interface (what client expects)
public interface PaymentGateway {
    PaymentResult charge(Money amount, PaymentInfo info);
    boolean refund(String transactionId, Money amount);
}

// Adaptee (existing class with incompatible interface)
public class StripeAPI {
    public ChargeResponse createCharge(double amount, String currency,
                                       String source) {
        // Stripe-specific implementation
        return new ChargeResponse("ch_123", "succeeded");
    }
    
    public RefundResponse createRefund(String chargeId, double amount) {
        // Stripe-specific implementation
        return new RefundResponse("re_456", "succeeded");
    }
}

// Adapter
public class StripeAdapter implements PaymentGateway {
    private final StripeAPI stripe;
    
    public StripeAdapter(StripeAPI stripe) {
        this.stripe = stripe;
    }
    
    @Override
    public PaymentResult charge(Money amount, PaymentInfo info) {
        // Translate from target to adaptee
        ChargeResponse response = stripe.createCharge(
            amount.getAmount(),
            amount.getCurrency(),
            info.getSource()
        );
        
        // Translate from adaptee to target
        return new PaymentResult(
            response.getStatus().equals("succeeded"),
            response.getId()
        );
    }
    
    @Override
    public boolean refund(String transactionId, Money amount) {
        RefundResponse response = stripe.createRefund(
            transactionId,
            amount.getAmount()
        );
        return response.getStatus().equals("succeeded");
    }
}

// Client uses target interface
PaymentGateway gateway = new StripeAdapter(new StripeAPI());
PaymentResult result = gateway.charge(new Money(100, "USD"), paymentInfo);

Data Translation

// Adapter translates between different data formats
public class WeatherAdapter implements StandardWeatherService {
    private final ExternalWeatherAPI externalApi;
    
    @Override
    public WeatherData getWeather(String city) {
        // Get from external API (different format)
        ExternalResponse response = externalApi.query(city);
        
        // Translate to our format
        return new WeatherData(
            response.getTemperature(),
            response.getHumidity(),
            translateCondition(response.getCode())
        );
    }
    
    private Condition translateCondition(int code) {
        return switch (code) {
            case 0 -> Condition.CLEAR;
            case 1, 2, 3 -> Condition.CLOUDY;
            case 45, 48 -> Condition.FOGGY;
            default -> Condition.UNKNOWN;
        };
    }
}

Class Adapter

Class Adapter uses multiple inheritance to inherit from both the target and the adaptee.

Structure

┌──────────────────────────┐
│     Target (interface)   │
└──────────┬───────────────┘
           │ implemented by
┌──────────┴───────────────┐
│       Adapter            │
│     (inherits both)      │
└──────────┬───────────────┘
           │ inherits from
┌──────────┴───────────────┐
│     Adaptee (class)      │
└──────────────────────────┘

Java Implementation (via interface + class)

// Target interface
public interface MediaPlayer {
    void play(String filename);
}

// Adaptee class
public class VLCPlayer {
    public void playVLC(String file) {
        System.out.println("Playing with VLC: " + file);
    }
}

// Class Adapter (Java doesn't support multiple inheritance)
// Use interface inheritance + class inheritance
public class VLCAdapter extends VLCPlayer implements MediaPlayer {
    @Override
    public void play(String filename) {
        playVLC(filename);  // Calls inherited method
    }
}

C++ Multiple Inheritance Example

// C++ supports multiple inheritance
class MediaPlayer {
public:
    virtual void play(const string& file) = 0;
};

class VLCPlayer {
public:
    void playVLC(const string& file) {
        cout << "Playing with VLC: " << file << endl;
    }
};

class VLCAdapter : public MediaPlayer, public VLCPlayer {
public:
    void play(const string& file) override {
        playVLC(file);  // Calls VLCPlayer method
    }
};

Object vs Class Adapter

Aspect Object Adapter Class Adapter
Mechanism Composition Inheritance
Flexibility Can adapt multiple adaptees Fixed to one adaptee
Override Cannot override adaptee Can override adaptee methods
Coupling Looser coupling Tighter coupling
Language Any OOP language Languages with multiple inheritance

When to Use Each

Use Object Adapter Use Class Adapter
Need to adapt at runtime Adaptee is known at compile time
Multiple adaptees possible Need to override adaptee behavior
Prefer composition Language supports multiple inheritance

Use Cases

Real-world applications of the Adapter pattern.

Third-Party API Integration

// Your system uses this interface
public interface EmailService {
    void send(String to, String subject, String body);
}

// Third-party has different API
public class SendGridAPI {
    public SendResponse sendEmail(SendGridMessage message) { ... }
}

public class MailgunAPI {
    public MailResponse send(MailgunMessage message) { ... }
}

// Adapters for each provider
public class SendGridAdapter implements EmailService {
    private final SendGridAPI sendGrid;
    
    public void send(String to, String subject, String body) {
        SendGridMessage msg = new SendGridMessage(to, subject, body);
        sendGrid.sendEmail(msg);
    }
}

public class MailgunAdapter implements EmailService {
    private final MailgunAPI mailgun;
    
    public void send(String to, String subject, String body) {
        MailgunMessage msg = new MailgunMessage(to, subject, body);
        mailgun.send(msg);
    }
}

Database Adapter

// Unified query interface
public interface DatabaseAdapter {
    List<Map<String, Object>> query(String sql, Object... params);
    int execute(String sql, Object... params);
}

// MySQL adapter
public class MySQLAdapter implements DatabaseAdapter {
    private final MySqlConnection conn;
    
    public List<Map<String, Object>> query(String sql, Object... params) {
        MySQLResultSet rs = conn.executeQuery(sql, params);
        return convertToMapList(rs);
    }
}

// PostgreSQL adapter
public class PostgresAdapter implements DatabaseAdapter {
    private final PostgresConnection conn;
    
    public List<Map<String, Object>> query(String sql, Object... params) {
        PostgresResultSet rs = conn.executeQuery(sql, params);
        return convertToMapList(rs);
    }
}

Legacy System Integration

// Modern interface
public interface OrderProcessor {
    OrderResult process(Order order);
}

// Legacy system with different interface
public class LegacyOrderSystem {
    public LegacyResponse submitLegacyOrder(LegacyOrderData data) { ... }
}

// Adapter bridges old and new
public class LegacyOrderAdapter implements OrderProcessor {
    private final LegacyOrderSystem legacySystem;
    
    public OrderResult process(Order order) {
        // Convert to legacy format
        LegacyOrderData legacyData = convertToLegacy(order);
        
        // Call legacy system
        LegacyResponse response = legacySystem.submitLegacyOrder(legacyData);
        
        // Convert response back
        return convertFromLegacy(response);
    }
}

Adapter vs Other Patterns

Pattern Use When
Adapter Interface incompatibility
Facade Simplify complex subsystem
Decorator Add behavior to existing object
Proxy Control access to object
Bridge Decouple abstraction from implementation

Practice Problems

0/3solved
Design Adapter Pattern System

Design a scalable Adapter Pattern system. Cover high-level architecture, data model, and API design.

Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliability
Adapter Pattern Scaling

How would you scale Adapter Pattern to handle 10x the current load? Identify bottlenecks and solutions.

Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decomposition
Adapter Pattern Failure Modes

Analyze potential failure modes for Adapter Pattern and design mitigation strategies.

Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradation

Quiz

1. What does the Adapter pattern do?

Question 1 options

2. What is the difference between Object and Class Adapter?

Question 2 options

3. When should you use an Adapter?

Question 3 options

4. What is the main disadvantage of Class Adapter?

Question 4 options

5. Adapter vs Facade?

Question 5 options

Flashcards

Question

What is the Adapter pattern?

Answer

Converts one interface to another, allowing classes with incompatible interfaces to work together. Uses composition or inheritance.

Question

Object Adapter vs Class Adapter?

Answer

Object: uses composition (wraps adaptee). Class: uses inheritance (extends both). Object is more flexible; Class can override adaptee.

Question

When to use Adapter?

Answer

Integrating third-party APIs, legacy system integration, database abstraction, or any time you need to convert between incompatible interfaces.

Question

Adapter vs Facade?

Answer

Adapter: converts one interface to another. Facade: simplifies complex subsystem with new simplified interface.

Question

Adapter vs Decorator?

Answer

Adapter: changes interface. Decorator: adds behavior while maintaining same interface.

Revision Notes

Key Takeaways

  • 1.Adapter converts one interface to another for compatibility
  • 2.Object Adapter uses composition; Class Adapter uses inheritance
  • 3.Adapter is essential for integrating third-party and legacy systems
  • 4.Adapter translates data formats between incompatible interfaces
  • 5.Choose Object Adapter for flexibility, Class Adapter for overriding

Interview Tips

  • Show Adapter pattern when discussing third-party API integration
  • Explain data translation between different formats
  • Discuss when to use Adapter vs Facade
  • Mention Adapter for legacy system integration

Cheat Sheet

Adapter Pattern - Cheat Sheet

Purpose:
Convert one interface to another for compatibility.

Types:

Type Mechanism Java Support
Object Composition
Class Multiple inheritance

Structure:

Client → Target → Adapter → Adaptee

Use Cases:

  • Third-party API integration
  • Legacy system integration
  • Database abstraction
  • Different format conversion

Adapter vs Others:

Pattern Use When
Adapter Interface conversion
Facade Simplify complex API
Decorator Add behavior
Proxy Control access