Skip to content
SP StackPractices
beginner

Adapter Pattern

Convert the interface of a class into another interface clients expect. A structural design pattern for interface compatibility.

Topics: design

Adapter Pattern

Overview

The Adapter Pattern is a structural design pattern that allows objects with incompatible interfaces to collaborate. It wraps an existing class with a new interface so that it becomes compatible with the client’s expectations.

It is the software equivalent of a physical power adapter: it converts one interface into another without modifying the original device.

When to Use

Use the Adapter Pattern when:

  • You want to use an existing class whose interface is incompatible with the rest of your code
  • You need to reuse legacy or third-party code that does not match your interfaces
  • You want to create a unified interface across several classes with different APIs
  • You cannot or should not modify the source code of the incompatible class
  • You need to translate data formats or calling conventions between systems

Solution

Python

class OldPrinter:
    def old_print(self, text: str):
        print(f"OldPrinter: {text}")

class PrinterAdapter:
    def __init__(self, old_printer: OldPrinter):
        self._old = old_printer

    def print(self, text: str):
        self._old.old_print(text)

# Usage
adapter = PrinterAdapter(OldPrinter())
adapter.print("Hello World")  # OldPrinter: Hello World

JavaScript

class OldPrinter {
  oldPrint(text) {
    console.log(`OldPrinter: ${text}`);
  }
}

class PrinterAdapter {
  constructor(oldPrinter) {
    this.old = oldPrinter;
  }

  print(text) {
    this.old.oldPrint(text);
  }
}

// Usage
const adapter = new PrinterAdapter(new OldPrinter());
adapter.print("Hello World"); // OldPrinter: Hello World

Java

class OldPrinter {
    void oldPrint(String text) {
        System.out.println("OldPrinter: " + text);
    }
}

interface ModernPrinter {
    void print(String text);
}

class PrinterAdapter implements ModernPrinter {
    private final OldPrinter oldPrinter;

    PrinterAdapter(OldPrinter oldPrinter) {
        this.oldPrinter = oldPrinter;
    }

    public void print(String text) {
        oldPrinter.oldPrint(text);
    }
}

// Usage
ModernPrinter printer = new PrinterAdapter(new OldPrinter());
printer.print("Hello World"); // OldPrinter: Hello World

Explanation

The Adapter Pattern consists of:

  • Target Interface (ModernPrinter): The interface the client expects
  • Adaptee (OldPrinter): The existing class with the incompatible interface
  • Adapter (PrinterAdapter): Wraps the adaptee and exposes the target interface

The adapter translates calls from the target interface into calls the adaptee understands. Neither the client nor the adaptee needs to change.

Variants

VariantUse CaseTrade-off
Object AdapterWraps an instance (composition)Flexible, can adapt subclasses
Class AdapterInherits from adaptee (multiple inheritance)Less flexible, not possible in all languages
Two-way AdapterBoth interfaces are usableMore complex, but bidirectional

Best Practices

  • Favor composition over inheritance for adapters (object adapter pattern)
  • Keep the adapter thin: It should translate calls, not add business logic
  • Document the mapping: Explain how target methods map to adaptee methods
  • Handle nulls and exceptions gracefully during translation
  • Consider caching: If translation involves heavy computation, cache results

Common Mistakes

  • Fat adapters: Adding business logic instead of just interface translation
  • Leaky adapters: Exposing adaptee methods through the adapter interface
  • Cascading adapters: Chaining multiple adapters creates indirection hell
  • Ignoring exceptions: Not translating or handling adaptee errors properly
  • Modifying the adaptee: The whole point is to leave the original class untouched

Frequently Asked Questions

Q: What is the difference between Adapter and Facade? A: Adapter makes one incompatible interface compatible. Facade simplifies a complex subsystem by providing a single unified interface.

Q: Can I adapt multiple classes at once? A: Yes. A single adapter can wrap multiple adaptees and coordinate them to provide a unified interface.

Q: Is Adapter a workaround for bad design? A: Sometimes, but often it is a pragmatic bridge when integrating external or legacy code that you cannot modify.