What is the Adapter Pattern?

The Adapter Pattern is a structural design pattern that allows objects with incompatible interfaces to work together. It acts as a wrapper that translates one interface into another that a client expects — like a power plug adapter that lets you plug a European device into an American socket.

Explanation

Real-World Analogy

  • Think of a travel power adapter 🔌:
    • Your laptop charger has a US plug (two flat pins).
    • The wall socket in Europe has a different shape.
    • The adapter sits between them, converting the interface — the laptop doesn’t change, the wall doesn’t change, the adapter bridges the gap.
  • In code: you have a new component expecting ILogger but the existing logger class exposes WriteLog() — the adapter wraps the old class and exposes ILogger.

Object Adapter vs Class Adapter

Object AdapterClass Adapter
MechanismComposition (has-a)Multiple inheritance / extends
FlexibilityCan adapt subclasses tooLimited to specific class
Preferred✅ Yes (follows composition over inheritance)❌ Only when necessary
LanguagesAll OOP languagesOnly languages with multiple inheritance

UML Structure

classDiagram
    class Target {
        <<interface>>
        +request()
    }
    class Adaptee {
        +specificRequest()
    }
    class Adapter {
        -adaptee: Adaptee
        +request()
    }
    class Client {
        +doWork(target: Target)
    }
    Target <|.. Adapter
    Adapter o--> Adaptee : wraps
    Client --> Target : uses

Implementation

  • Integrating a legacy XML analytics API with a new JSON-based analytics interface. The adapter translates between them without changing either side. Python · Cpp · Java · Java Script · CSharp

    Languages:

# ─── Python ──────────────────────────────────────────────────────────
# Scenario: new code expects IAnalytics (JSON-based),
# but LegacyXMLAnalytics speaks XML only.
from abc import ABC, abstractmethod
import json, xml.etree.ElementTree as ET
 
# ── Target Interface (what the new system expects) ─────────────────
class IAnalytics(ABC):
    @abstractmethod
    def track_event(self, event: dict) -> None: ...
 
    @abstractmethod
    def get_report(self) -> dict: ...
 
# ── Adaptee (legacy system with XML interface) ─────────────────────
class LegacyXMLAnalytics:
    """Old library — speaks only XML, cannot be changed."""
    def __init__(self):
        self._events: list[str] = []
 
    def log_xml_event(self, xml_str: str) -> None:
        self._events.append(xml_str)
        print(f"[Legacy XML] Logged: {xml_str}")
 
    def export_xml_report(self) -> str:
        events_xml = "".join(f"<event>{e}</event>" for e in self._events)
        return f"<report><count>{len(self._events)}</count>{events_xml}</report>"
 
# ── Adapter (Object Adapter — uses composition) ────────────────────
class XMLAnalyticsAdapter(IAnalytics):
    """Translates IAnalytics calls → LegacyXMLAnalytics calls."""
    def __init__(self):
        self._legacy = LegacyXMLAnalytics()   # Wrap the adaptee
 
    def track_event(self, event: dict) -> None:
        # Convert JSON dict → XML string for the legacy system
        xml_str = "<event>"
        for k, v in event.items():
            xml_str += f"<{k}>{v}</{k}>"
        xml_str += "</event>"
        self._legacy.log_xml_event(xml_str)
 
    def get_report(self) -> dict:
        # Convert XML report → JSON dict for the new system
        xml_report = self._legacy.export_xml_report()
        root = ET.fromstring(xml_report)
        return {
            "total_events": int(root.find("count").text),
            "format": "adapted_from_xml"
        }
 
# ── New Analytics Service (speaks IAnalytics only) ─────────────────
class AnalyticsDashboard:
    def __init__(self, analytics: IAnalytics):
        self.analytics = analytics
 
    def record_page_view(self, page: str, user_id: str) -> None:
        self.analytics.track_event({"type": "page_view", "page": page, "user": user_id})
 
    def show_report(self) -> None:
        report = self.analytics.get_report()
        print(f"Dashboard Report: {report}")
 
# ── Client ─────────────────────────────────────────────────────────
# Plug legacy XML system into new JSON-expecting dashboard
adapter = XMLAnalyticsAdapter()
dashboard = AnalyticsDashboard(adapter)   # Client uses IAnalytics interface only
 
dashboard.record_page_view("/home", "user-42")
dashboard.record_page_view("/about", "user-99")
dashboard.show_report()
// ─── C++ ─────────────────────────────────────────────────────────────
#include <iostream>
#include <string>
#include <map>
 
// ── Target Interface ──────────────────────────────────────────────────
class ILogger {
public:
    virtual void log(const std::string& level, const std::string& msg) = 0;
    virtual ~ILogger() = default;
};
 
// ── Adaptee (legacy logger with different interface) ───────────────────
class LegacyLogger {
public:
    void writeLog(int severity, const std::string& message) {
        static std::map<int, std::string> levels{{0,"INFO"},{1,"WARN"},{2,"ERROR"}};
        std::cout << "[Legacy:" << levels[severity] << "] " << message << "\n";
    }
};
 
// ── Adapter (Object Adapter) ──────────────────────────────────────────
class LegacyLoggerAdapter : public ILogger {
    LegacyLogger legacy_;
public:
    void log(const std::string& level, const std::string& msg) override {
        int severity = (level == "ERROR") ? 2 : (level == "WARN") ? 1 : 0;
        legacy_.writeLog(severity, msg);   // Translate & delegate
    }
};
 
// ── New Application (uses ILogger) ────────────────────────────────────
class Application {
    ILogger& logger_;
public:
    Application(ILogger& logger) : logger_(logger) {}
    void startup()  { logger_.log("INFO", "Application started"); }
    void error()    { logger_.log("ERROR", "Something failed"); }
};
 
int main() {
    LegacyLoggerAdapter adapter;
    Application app(adapter);   // Works with ILogger interface
    app.startup();
    app.error();
}
// ─── Java ─────────────────────────────────────────────────────────────
 
// ── Target Interface ─────────────────────────────────────────────────
interface ILogger {
    void log(String level, String message);
}
 
// ── Adaptee (legacy system) ──────────────────────────────────────────
class LegacyLogger {
    public void writeLog(int severity, String message) {
        String[] levels = {"INFO", "WARN", "ERROR"};
        System.out.printf("[Legacy:%s] %s%n", levels[severity], message);
    }
}
 
// ── Adapter (wraps LegacyLogger) ─────────────────────────────────────
class LegacyLoggerAdapter implements ILogger {
    private final LegacyLogger legacy = new LegacyLogger();
 
    @Override
    public void log(String level, String message) {
        int severity = switch (level) {
            case "ERROR" -> 2;
            case "WARN"  -> 1;
            default      -> 0;
        };
        legacy.writeLog(severity, message);
    }
}
 
// ── Application (uses ILogger) ────────────────────────────────────────
class Application {
    private final ILogger logger;
 
    Application(ILogger logger) { this.logger = logger; }
    void startup() { logger.log("INFO",  "Application started"); }
    void error()   { logger.log("ERROR", "Something failed"); }
}
 
class AdapterDemo {
    public static void main(String[] args) {
        ILogger adapter = new LegacyLoggerAdapter();
        Application app = new Application(adapter);
        app.startup();
        app.error();
    }
}
// ─── JavaScript ───────────────────────────────────────────────────────
 
// ── Adaptee (legacy console-based logger) ─────────────────────────────
class LegacyConsoleLogger {
    writeLog(severity, message) {
        const levels = ['INFO', 'WARN', 'ERROR'];
        console.log(`[Legacy:${levels[severity]}] ${message}`);
    }
}
 
// ── Adapter ────────────────────────────────────────────────────────────
class LegacyLoggerAdapter {
    #legacy = new LegacyConsoleLogger();
 
    log(level, message) {
        const severity = level === 'ERROR' ? 2 : level === 'WARN' ? 1 : 0;
        this.#legacy.writeLog(severity, message);
    }
}
 
// ── Application (expects logger with .log(level, message)) ────────────
class Application {
    constructor(logger) { this.logger = logger; }
    startup() { this.logger.log('INFO',  'Application started'); }
    error()   { this.logger.log('ERROR', 'Something failed'); }
}
 
const adapter = new LegacyLoggerAdapter();
const app = new Application(adapter);
app.startup();
app.error();
// ─── C# ──────────────────────────────────────────────────────────────
using System;
 
// ── Target Interface ──────────────────────────────────────────────────
interface ILogger {
    void Log(string level, string message);
}
 
// ── Adaptee ───────────────────────────────────────────────────────────
class LegacyLogger {
    public void WriteLog(int severity, string message) {
        string[] levels = { "INFO", "WARN", "ERROR" };
        Console.WriteLine($"[Legacy:{levels[severity]}] {message}");
    }
}
 
// ── Adapter ───────────────────────────────────────────────────────────
class LegacyLoggerAdapter : ILogger {
    readonly LegacyLogger legacy = new();
 
    public void Log(string level, string message) {
        int severity = level switch {
            "ERROR" => 2,
            "WARN"  => 1,
            _       => 0
        };
        legacy.WriteLog(severity, message);
    }
}
 
class Application {
    readonly ILogger logger;
    public Application(ILogger logger) { this.logger = logger; }
    public void Startup() => logger.Log("INFO",  "Application started");
    public void Error()   => logger.Log("ERROR", "Something failed");
 
    static void Main() {
        ILogger adapter = new LegacyLoggerAdapter();
        var app = new Application(adapter);
        app.Startup();
        app.Error();
    }
}

Key Takeaways

  • Bridges incompatible interfaces without modifying either the client or the adaptee.
  • Uses composition (Object Adapter) — wraps the adaptee, translates calls, delegates.
  • Essential for legacy integration and third-party library integration.
  • Follows the Open/Closed Principle: extend behavior without modifying existing classes.
  • Related: Decorator Pattern, Facade Pattern, Proxy Pattern

More Learn

GitHub & Webs