Adapter Design Pattern
Adapter Design Pattern is a structural pattern that acts as a bridge between two incompatible interfaces, allowing them to work together. It is especially useful for integrating legacy code or third-party libraries into a new system.
Let's understand this with the help of diagram:

Explain the Diagram:
- Client wants to use a Target interface (it calls
Request()). - Adaptee already has useful functionality, but its method (
SpecificRequest()) doesn’t match theTargetinterface. - Adapter acts as a bridge: it implements the
Targetinterface (Request()), but inside, it calls theAdaptee’sSpecificRequest(). - This allows the Client to use the Adaptee without changing its code.
Real-World Example of Adapter Design Pattern
Adapter Design Pattern can be seen as a mobile adapter that helps in matching the device's power and voltage requirements. Similarly in software development, we use adapter for enabling interoperability between incompatible interfaces
- Software that use different file formats (like CSV, JSON, XML) uses adapters to convert these into a format that the application can work with, facilitating data interoperability.
- Device Drivers in Operating Systems, Database Connectors and Language Converters (Chinese to English and English to Hindi) are more examples of adapters.
- In situations where we have a major change in new vs old APIs, instead of writing the whole old code again, we can use an adapter that does the conversion.
Pros of Adapter Design Pattern
Below are the pros of Adapter Design Pattern:
- Promotes code reuse without modification.
- Keeps classes focused on core logic by isolating adaptation.
- Supports multiple interfaces through interchangeable adapters.
- Decouples system from implementations, easing modifications and swaps.
Cons of Adapter Design Pattern
Below are the cons of Adapter Design Pattern:
- Adds complexity and can make code harder to follow.
- Introduces slight performance overhead due to extra indirection.
- Multiple adapters increase maintenance effort.
- Risk of overuse for minor changes, leading to unnecessary complexity.
- Handling many interfaces may require multiple adapters, complicating design.
Uses Of Adapter Design Pattern
We can use adapter design pattern when:
- Enables communication between incompatible systems.
- Reuses existing code or libraries without rewriting.
- Simplifies integration of new components, keeping the system flexible.
- Centralizes compatibility changes, making maintenance easier and safer.
When not to use Adapter Design Pattern?
Do not use adapter design pattern when:
- If the system is straightforward and all components are compatible, an adapter may be unnecessary.
- Adapters can introduce a slight overhead, which might be a concern in performance-sensitive environments.
- When there are no issues with interface compatibility, using an adapter can be redundant.
- For projects with a very short lifespan, the overhead of implementing an adapter might not be worth it.
Components of Adapter Design Pattern
Below are the components of adapter design pattern:
- Target Interface: The interface expected by the client, defining the operations it can use.
- Adaptee: The existing class with an incompatible interface that needs integration.
- Adapter: Implements the target interface and uses the adaptee internally, acting as a bridge.
- Client: Uses the target interface, unaware of the adapter or adaptee details.
Different implementations of Adapter Design Pattern
The Adapter Design Pattern can be applied in various ways depending on the programming language and the specific context. Here are the primary implementations:
1. Class Adapter (Inheritance-based)
- In this approach, the adapter class inherits from both the target interface (the one the client expects) and the adaptee (the existing class needing adaptation).
- Programming languages that allow multiple inheritance, like C++, are more likely to use this technique.
- However, in languages like Java and C#, which do not support multiple inheritance, this approach is less frequently used.
2. Object Adapter (Composition-based)
- The object adapter employs composition instead of inheritance. In this implementation, the adapter holds an instance of the adaptee and implements the target interface.
- This approach is more flexible as it allows a single adapter to work with multiple adaptees and does not require the complexities of inheritance.
- The object adapter is widely used in languages like Java and C#.
3. Two-way Adapter
- A two-way adapter can function as both a target and an adaptee, depending on which interface is being invoked.
- This type of adapter is particularly useful when two systems need to work together and require mutual adaptation.
4. Interface Adapter (Default Adapter)
- When only a few methods from an interface are necessary, an interface adapter can be employed.
- This is especially useful in cases where the interface contains many methods, and the adapter provides default implementations for those that are not needed.
- This approach is often seen in languages like Java, where abstract classes or default method implementations in interfaces simplify the implementation process.
How Adapter Design Pattern works?
Below is how adapter design pattern works:
- Step 1: The client initiates a request by calling a method on the adapter via the target interface.
- Step 2: The adapter maps or transforms the client's request into a format that the adaptee can understand using the adaptee's interface.
- Step 3: The adaptee does the actual job based on the translated request from the adapter.
- Step 4: The client receives the results of the call, remaining unaware of the adapter's presence or the specific details of the adaptee.
Adapter Design Pattern Example
Let's understand adapter design pattern through an example:
Problem Statement:
Let's consider a scenario where we have an existing system that uses a
LegacyPrinterclass with a method namedprintDocument()which we want to adapt into a new system that expects aPrinterinterface with a method namedprint(). We'll use the Adapter design pattern to make these two interfaces compatible.

1. Target Interface (Printer)
The interface that the client code expects.
// Target Interface
class Printer {
public:
virtual void print() = 0; // Pure virtual function (abstract method)
};
/* Target Interface */
interface Printer {
void print();
}
""" Target Interface """
from abc import ABC, abstractmethod
class Printer(ABC):
@abstractmethod
def print(self):
pass
// Target Interface
public interface Printer {
void Print();
}
// Target Interface
class Printer {
print() {
throw new Error('Method not implemented.');
}
}
2. Adaptee (LegacyPrinter)
The existing class with an incompatible interface.
// Adaptee
class LegacyPrinter {
public:
void printDocument() {
std::cout << "Legacy Printer is printing a document." << std::endl;
}
};
/* Adaptee */
class LegacyPrinter {
public void printDocument() {
System.out.println("Legacy Printer is printing a document.");
}
}
# Adaptee
class LegacyPrinter:
def print_document(self):
print('Legacy Printer is printing a document.')
// Adaptee
class LegacyPrinter {
public void PrintDocument() {
System.Console.WriteLine("Legacy Printer is printing a document.");
}
}
// Adaptee
class LegacyPrinter {
printDocument() {
console.log('Legacy Printer is printing a document.');
}
}
3. Adapter (PrinterAdapter)
The class that adapts the LegacyPrinter to the Printer interface.
// Adapter
class PrinterAdapter : public Printer {
private:
LegacyPrinter legacyPrinter;
public:
void print() override {
legacyPrinter.printDocument();
}
};
/* Adapter */
class PrinterAdapter implements Printer {
private LegacyPrinter legacyPrinter;
public PrinterAdapter(LegacyPrinter legacyPrinter) {
this.legacyPrinter = legacyPrinter;
}
@Override
public void print() {
legacyPrinter.printDocument();
}
}
# Adapter
class PrinterAdapter(Printer):
def __init__(self, legacy_printer):
self.legacy_printer = legacy_printer
def print(self):
self.legacy_printer.print_document()
// Adapter
class PrinterAdapter : Printer {
private LegacyPrinter legacyPrinter;
public PrinterAdapter(LegacyPrinter legacyPrinter) {
this.legacyPrinter = legacyPrinter;
}
public override void Print() {
legacyPrinter.PrintDocument();
}
}
// Adapter
class PrinterAdapter {
constructor(legacyPrinter) {
this.legacyPrinter = legacyPrinter;
}
print() {
this.legacyPrinter.printDocument();
}
}
4. Client Code
The code that interacts with the Printer interface.
// Client Code
void clientCode(Printer& printer) {
printer.print();
}
/* Client Code */
interface Printer {
void print();
}
void clientCode(Printer printer) {
printer.print();
}
#
# Client Code
#
from abc import ABC, abstractmethod
class Printer(ABC):
@abstractmethod
def print(self):
pass
def clientCode(printer):
printer.print()
// Client Code
using System;
interface Printer {
void Print();
}
class Client {
public static void ClientCode(Printer printer) {
printer.Print();
}
}
// Client Code
function clientCode(printer) {
printer.print();
}
Complete Code for the above example:
// Adapter Design Pattern Example Code
#include <iostream>
// Target Interface
class Printer {
public:
virtual void print() = 0;
};
// Adaptee
class LegacyPrinter {
public:
void printDocument() {
std::cout << "Legacy Printer is printing a document." <<
std::endl;
}
};
// Adapter
class PrinterAdapter : public Printer {
private:
LegacyPrinter legacyPrinter;
public:
void print() override {
legacyPrinter.printDocument();
}
};
// Client Code
void clientCode(Printer& printer) {
printer.print();
}
int main() {
// Using the Adapter
PrinterAdapter adapter;
clientCode(adapter);
return 0;
}
/* Adapter Design Pattern Example Code */
// Target Interface
interface Printer {
void print();
}
// Adaptee
class LegacyPrinter {
public void printDocument() {
System.out.println("Legacy Printer is printing a document.");
}
}
// Adapter
class PrinterAdapter implements Printer {
private LegacyPrinter legacyPrinter = new LegacyPrinter();
@Override
public void print() {
legacyPrinter.printDocument();
}
}
// Client Code
public class Client {
public static void clientCode(Printer printer) {
printer.print();
}
public static void main(String[] args) {
// Using the Adapter
PrinterAdapter adapter = new PrinterAdapter();
clientCode(adapter);
}
}
''' Adapter Design Pattern Example Code '''
# Target Interface
from abc import ABC, abstractmethod
class Printer(ABC):
@abstractmethod
def print(self):
pass
# Adaptee
class LegacyPrinter:
def print_document(self):
print('Legacy Printer is printing a document.')
# Adapter
class PrinterAdapter(Printer):
def __init__(self):
self.legacy_printer = LegacyPrinter()
def print(self):
self.legacy_printer.print_document()
# Client Code
def client_code(printer):
printer.print()
if __name__ == '__main__':
# Using the Adapter
adapter = PrinterAdapter()
client_code(adapter)
/* Adapter Design Pattern Example Code */
// Target Interface
public interface IPrinter
{
void Print();
}
// Adaptee
public class LegacyPrinter
{
public void PrintDocument()
{
System.Console.WriteLine("Legacy Printer is printing a document.");
}
}
// Adapter
public class PrinterAdapter : IPrinter
{
private LegacyPrinter legacyPrinter = new LegacyPrinter();
public void Print()
{
legacyPrinter.PrintDocument();
}
}
// Client Code
public class Client
{
public static void ClientCode(IPrinter printer)
{
printer.Print();
}
public static void Main(string[] args)
{
// Using the Adapter
PrinterAdapter adapter = new PrinterAdapter();
ClientCode(adapter);
}
}
/* Adapter Design Pattern Example Code */
// Target Interface
const Printer = {
print: function() {
throw new Error('print method must be implemented!');
}
};
// Adaptee
class LegacyPrinter {
printDocument() {
console.log('Legacy Printer is printing a document.');
}
}
// Adapter
class PrinterAdapter {
constructor() {
this.legacyPrinter = new LegacyPrinter();
}
print() {
this.legacyPrinter.printDocument();
}
}
// Client Code
class Client {
static clientCode(printer) {
printer.print();
}
static main() {
// Using the Adapter
const adapter = new PrinterAdapter();
this.clientCode(adapter);
}
}
// Running the client code
Client.main();
Output
Legacy Printer is printing a document.