Hubbry Logo
Adapter patternAdapter patternMain
Open search
Adapter pattern
Community hub
Adapter pattern
logo
7 pages, 0 posts
0 subscribers
Be the first to start a discussion here.
Be the first to start a discussion here.
Adapter pattern
Adapter pattern
from Wikipedia

In software engineering, the adapter pattern is a software design pattern (also known as wrapper, an alternative naming shared with the decorator pattern) that allows the interface of an existing class to be used as another interface.[1] It is often used to make existing classes work with others without modifying their source code.

An example is an adapter that converts the interface of a Document Object Model of an XML document into a tree structure that can be displayed.

Overview

[edit]

The adapter[2] design pattern is one of the twenty-three well-known Gang of Four design patterns that describe how to solve recurring design problems to design flexible and reusable object-oriented software, that is, objects that are easier to implement, change, test, and reuse.

The adapter design pattern solves problems like:[3]

  • How can a class be reused that does not have an interface that a client requires?
  • How can classes that have incompatible interfaces work together?
  • How can an alternative interface be provided for a class?

Often an (already existing) class can not be reused only because its interface does not conform to the interface clients require.

The adapter design pattern describes how to solve such problems:

  • Define a separate adapter class that converts the (incompatible) interface of a class (adaptee) into another interface (target) clients require.
  • Work through an adapter to work with (reuse) classes that do not have the required interface.

The key idea in this pattern is to work through a separate adapter that adapts the interface of an (already existing) class without changing it.

Clients don't know whether they work with a target class directly or through an adapter with a class that does not have the target interface.

See also the UML class diagram below.

Definition

[edit]

An adapter allows two incompatible interfaces to work together. This is the real-world definition for an adapter. Interfaces may be incompatible, but the inner functionality should suit the need. The adapter design pattern allows otherwise incompatible classes to work together by converting the interface of one class into an interface expected by the clients.

Usage

[edit]

An adapter can be used when the wrapper must respect a particular interface and must support polymorphic behavior. Alternatively, a decorator makes it possible to add or alter behavior of an interface at run-time, and a facade is used when an easier or simpler interface to an underlying object is desired.[4]

Pattern Intent
Adapter or wrapper Converts one interface to another so that it matches what the client is expecting
Decorator Dynamically adds responsibility to the interface by wrapping the original code
Delegation Support "composition over inheritance"
Facade Provides a simplified interface

Structure

[edit]

UML class diagram

[edit]
A sample UML class diagram for the adapter design pattern.[5]

In the above UML class diagram, the client class that requires a target interface cannot reuse the adaptee class directly because its interface doesn't conform to the target interface. Instead, the client works through an adapter class that implements the target interface in terms of adaptee:

  • The object adapter way implements the target interface by delegating to an adaptee object at run-time (adaptee.specificOperation()).
  • The class adapter way implements the target interface by inheriting from an adaptee class at compile-time (specificOperation()).

Object adapter pattern

[edit]

In this adapter pattern, the adapter contains an instance of the class it wraps. In this situation, the adapter makes calls to the instance of the wrapped object.

The object adapter pattern expressed in UML
The object adapter pattern expressed in LePUS3

Class adapter pattern

[edit]

This adapter pattern uses multiple polymorphic interfaces implementing or inheriting both the interface that is expected and the interface that is pre-existing. It is typical for the expected interface to be created as a pure interface class, especially in languages such as Java (before JDK 1.8) that do not support multiple inheritance of classes.[1]

The class adapter pattern expressed in UML.
The class adapter pattern expressed in LePUS3

A further form of runtime adapter pattern

[edit]

Motivation from compile time solution

[edit]

It is desired for classA to supply classB with some data, let us suppose some String data. A compile time solution is:

classB.setStringData(classA.getStringData());

However, suppose that the format of the string data must be varied. A compile time solution is to use inheritance:

public class Format1ClassA extends ClassA {
    @Override
    public String getStringData() {
        return format(toString());
    }
}

and perhaps create the correctly "formatting" object at runtime by means of the factory pattern.

Run-time adapter solution

[edit]

A solution using "adapters" proceeds as follows:

  1. Define an intermediary "provider" interface, and write an implementation of that provider interface that wraps the source of the data, ClassA in this example, and outputs the data formatted as appropriate:
    public interface StringProvider {
        public String getStringData();
    }
    
    public class ClassAFormat1 implements StringProvider {
        private ClassA classA = null;
    
        public ClassAFormat1(final ClassA a) {
            classA = a;
        }
    
        public String getStringData() {
            return format(classA.getStringData());
        }
    
        private String format(final String sourceValue) {
            // Manipulate the source string into a format required 
            // by the object needing the source object's data
            return sourceValue.trim();
        }
    }
    
  2. Write an adapter class that returns the specific implementation of the provider:
    public class ClassAFormat1Adapter extends Adapter {
        public Object adapt(final Object anObject) {
            return new ClassAFormat1((ClassA) anObject);
        }
    }
    
  3. Register the adapter with a global registry, so that the adapter can be looked up at runtime:
    AdapterFactory.getInstance().registerAdapter(ClassA.class, ClassAFormat1Adapter.class, "format1");
    
  4. In code, when wishing to transfer data from ClassA to ClassB, write:
    Adapter adapter = AdapterFactory.getInstance()
        .getAdapterFromTo(ClassA.class, StringProvider.class, "format1");
    StringProvider provider = (StringProvider) adapter.adapt(classA);
    String string = provider.getStringData();
    classB.setStringData(string);
    

    or more concisely:

    classB.setStringData(((StringProvider)AdapterFactory.getInstance()
        .getAdapterFromTo(ClassA.class, StringProvider.class, "format1")
        .adapt(classA))
        .getStringData()
    );
    
  5. The advantage can be seen in that, if it is desired to transfer the data in a second format, then look up the different adapter/provider:
    Adapter adapter = AdapterFactory.getInstance()
        .getAdapterFromTo(ClassA.class, StringProvider.class, "format2");
    
  6. And if it is desired to output the data from ClassA as, say, image data in Class C:
    Adapter adapter = AdapterFactory.getInstance()
        .getAdapterFromTo(ClassA.class, ImageProvider.class, "format2");
    ImageProvider provider = (ImageProvider) adapter.adapt(classA);
    classC.setImage(provider.getImage());
    
  7. In this way, the use of adapters and providers allows multiple "views" by ClassB and ClassC into ClassA without having to alter the class hierarchy. In general, it permits a mechanism for arbitrary data flows between objects that can be retrofitted to an existing object hierarchy.

Implementation of the adapter pattern

[edit]

When implementing the adapter pattern, for clarity, one can apply the class name [ClassName]To[Interface]Adapter to the provider implementation; for example, DAOToProviderAdapter. It should have a constructor method with an adaptee class variable as a parameter. This parameter will be passed to an instance member of [ClassName]To[Interface]Adapter. When the clientMethod is called, it will have access to the adaptee instance that allows for accessing the required data of the adaptee and performing operations on that data that generates the desired output.

Java

[edit]
interface ILightningPhone {
    void recharge();
    void useLightning();
}

interface IMicroUsbPhone {
    void recharge();
    void useMicroUsb();
}

class Iphone implements ILightningPhone {
    private boolean connector;

    @Override
    public void useLightning() {
        connector = true;
        System.out.println("Lightning connected");
    }

    @Override
    public void recharge() {
        if (connector) {
            System.out.println("Recharge started");
            System.out.println("Recharge finished");
        } else {
            System.out.println("Connect Lightning first");
        }
    }
}

class Android implements IMicroUsbPhone {
    private boolean connector;

    @Override
    public void useMicroUsb() {
        connector = true;
        System.out.println("MicroUsb connected");
    }

    @Override
    public void recharge() {
        if (connector) {
            System.out.println("Recharge started");
            System.out.println("Recharge finished");
        } else {
            System.out.println("Connect MicroUsb first");
        }
    }
}
/* exposing the target interface while wrapping source object */
class LightningToMicroUsbAdapter implements IMicroUsbPhone {
    private final ILightningPhone lightningPhone;

    public LightningToMicroUsbAdapter(ILightningPhone lightningPhone) {
        this.lightningPhone = lightningPhone;
    }

    @Override
    public void useMicroUsb() {
        System.out.println("MicroUsb connected");
        lightningPhone.useLightning();
    }

    @Override
    public void recharge() {
        lightningPhone.recharge();
    }
}

public class AdapterDemo {
    static void rechargeMicroUsbPhone(IMicroUsbPhone phone) {
        phone.useMicroUsb();
        phone.recharge();
    }

    static void rechargeLightningPhone(ILightningPhone phone) {
        phone.useLightning();
        phone.recharge();
    }

    public static void main(String[] args) {
        Android android = new Android();
        Iphone iPhone = new Iphone();

        System.out.println("Recharging android with MicroUsb");
        rechargeMicroUsbPhone(android);

        System.out.println("Recharging iPhone with Lightning");
        rechargeLightningPhone(iPhone);

        System.out.println("Recharging iPhone with MicroUsb");
        rechargeMicroUsbPhone(new LightningToMicroUsbAdapter(iPhone));
    }
}

Output

Recharging android with MicroUsb
MicroUsb connected
Recharge started
Recharge finished
Recharging iPhone with Lightning
Lightning connected
Recharge started
Recharge finished
Recharging iPhone with MicroUsb
MicroUsb connected
Lightning connected
Recharge started
Recharge finished

Python

[edit]
"""
Adapter pattern example.
"""
from abc import ABCMeta, abstractmethod
from typing import NoReturn

RECHARGE: list[str] = ["Recharge started.", "Recharge finished."]
POWER_ADAPTERS: dict[str, str] = {"Android": "MicroUSB", "iPhone": "Lightning"}
CONNECTED_MSG: str = "{} connected."
CONNECT_FIRST_MSG: str = "Connect {} first."

class RechargeTemplate(metaclass = ABCMeta):
    @abstractmethod
    def recharge(self) -> NoReturn:
        raise NotImplementedError("You should implement this.")

class FormatIPhone(RechargeTemplate):
    @abstractmethod
    def use_lightning(self) -> NoReturn:
        raise NotImplementedError("You should implement this.")

class FormatAndroid(RechargeTemplate):
    @abstractmethod
    def use_micro_usb(self) -> NoReturn:
        raise NotImplementedError("You should implement this.")

class IPhone(FormatIPhone):
    __name__: str = "iPhone"

    def __init__(self):
        self.connector: bool = False

    def use_lightning(self) -> None:
        self.connector = True
        print(CONNECTED_MSG.format(POWER_ADAPTERS[self.__name__]))

    def recharge(self) -> None:
        if self.connector:
            for state in RECHARGE:
                print(state)
        else:
            print(CONNECT_FIRST_MSG.format(POWER_ADAPTERS[self.__name__]))

class Android(FormatAndroid):
    __name__: str = "Android"

    def __init__(self) -> None:
        self.connector: bool = False

    def use_micro_usb(self) -> None:
        self.connector = True
        print(CONNECTED_MSG.format(POWER_ADAPTERS[self.__name__]))

    def recharge(self) -> None:
        if self.connector:
            for state in RECHARGE:
                print(state)
        else:
            print(CONNECT_FIRST_MSG.format(POWER_ADAPTERS[self.__name__]))

class IPhoneAdapter(FormatAndroid):
    def __init__(self, mobile: FormatAndroid) -> None:
        self.mobile: FormatAndroid = mobile

    def recharge(self) -> None:
        self.mobile.recharge()

    def use_micro_usb(self) -> None:
        print(CONNECTED_MSG.format(POWER_ADAPTERS["Android"]))
        self.mobile.use_lightning()

class AndroidRecharger:
    def __init__(self) -> None:
        self.phone: Android = Android()
        self.phone.use_micro_usb()
        self.phone.recharge()

class IPhoneMicroUSBRecharger:
    def __init__(self) -> None:
        self.phone: IPhone = IPhone()
        self.phone_adapter: IPhoneAdapter = IPhoneAdapter(self.phone)
        self.phone_adapter.use_micro_usb()
        self.phone_adapter.recharge()

class IPhoneRecharger:
    def __init__(self) -> None:
        self.phone: IPhone = IPhone()
        self.phone.use_lightning()
        self.phone.recharge()

print("Recharging Android with MicroUSB recharger.")
AndroidRecharger()
print()

print("Recharging iPhone with MicroUSB using adapter pattern.")
IPhoneMicroUSBRecharger()
print()

print("Recharging iPhone with iPhone recharger.")
IPhoneRecharger()

C#

[edit]
public interface ILightningPhone
{
	void ConnectLightning();
	void Recharge();
}

public interface IUsbPhone
{
	void ConnectUsb();
	void Recharge();
}

public sealed class AndroidPhone : IUsbPhone
{
	private bool isConnected;

	public void ConnectUsb()
	{
		this.isConnected = true;
		Console.WriteLine("Android phone connected.");
	}

	public void Recharge()
	{
		if (this.isConnected)
		{
			Console.WriteLine("Android phone recharging.");
		}
		else
		{
			Console.WriteLine("Connect the USB cable first.");
		}
	}
}

public sealed class ApplePhone : ILightningPhone
{
	private bool isConnected;

	public void ConnectLightning()
	{
		this.isConnected = true;
		Console.WriteLine("Apple phone connected.");
	}

	public void Recharge()
	{
		if (this.isConnected)
		{
			Console.WriteLine("Apple phone recharging.");
		}
		else
		{
			Console.WrizteLine("Connect the Lightning cable first.");
		}
	}
}

public sealed class LightningToUsbAdapter : IUsbPhone
{
	private readonly ILightningPhone lightningPhone;

	private bool isConnected;

	public LightningToUsbAdapter(ILightningPhone lightningPhone)
	{
		this.lightningPhone = lightningPhone;
	}

	public void ConnectUsb()
	{
		this.lightningPhone.ConnectLightning();
	}

	public void Recharge()
	{
		this.lightningPhone.Recharge();
	}
}

public void Main()
{
	ILightningPhone applePhone = new ApplePhone();
	IUsbPhone adapterCable = new LightningToUsbAdapter(applePhone);
	adapterCable.ConnectUsb();
	adapterCable.Recharge();
}

Output:

Apple phone connected.
Apple phone recharging.

See also

[edit]

References

[edit]
Revisions and contributorsEdit on WikipediaRead on Wikipedia
from Grokipedia
The Adapter pattern is a structural design pattern in that enables objects with incompatible interfaces to collaborate by converting the interface of an existing class (the adaptee) into another interface that clients expect, thereby allowing otherwise incompatible classes to work together seamlessly. The primary intent of the Adapter pattern is to provide compatibility between legacy or third-party components and new systems without modifying the original code, addressing interface mismatches that arise in scenarios such as integrating libraries from different vendors or adapting data formats across modules. This motivation stems from real-world analogies like electrical adapters that allow plugs from one country to fit outlets in another, or software examples where an existing class offers valuable functionality but exposes methods that do not align with the client's required protocol. In terms of structure, the pattern involves four key participants: the client, which interacts with the target interface; the Target, defining the interface that the client expects; the Adapter, which implements the Target interface and wraps the Adaptee (the incompatible class) using composition to translate calls; and the Adaptee itself, whose methods are delegated and adapted as needed. There are two main variants: the object adapter, which relies on composition for flexibility and is preferred in languages like or C# that lack ; and the class adapter, which uses to adapt the adaptee but is less portable across languages. The Adapter pattern offers several benefits, including the reuse of existing classes without alteration, promotion of through , and isolation of adaptation logic in a dedicated class, which enhances in large systems. It is commonly applied in interoperability scenarios, such as bridging .NET managed code with components via runtime callable wrappers that translate strings and exceptions, or in simulations where a turkey object is adapted to behave like a by mapping its "gobble" method to "quack."

Fundamentals

Definition

The Adapter pattern is a structural design pattern that converts the interface of a class into another interface that clients expect, allowing otherwise incompatible classes to collaborate without modifying their source code. This pattern acts as a bridge between two incompatible systems, enabling the reuse of existing functionality in new contexts by wrapping the incompatible component in a compatible wrapper. The pattern consists of four key components: the Target, which defines the interface that clients expect and use; the Client, which relies on the Target interface for its operations; the Adaptee, which is the existing class or component with an incompatible interface; and the Adapter, which implements the Target interface while internally delegating calls to the Adaptee, thereby translating requests between the two. As described by the , the primary intent of the Adapter pattern is to adapt the interfaces of existing classes for reuse in client code, to support integration with multiple versions of third-party libraries through version-specific adapters, and to enable the creation of reusable classes that can cooperate with unrelated or unforeseen classes without tight coupling. The pattern was first formalized in the 1994 book Design Patterns: Elements of Reusable Object-Oriented Software by , Richard Helm, Ralph Johnson, and John Vlissides, known as the .

Motivation

The arises from the common challenge in where an existing class, referred to as the adaptee, provides functionality that is valuable but exposes an interface incompatible with the expectations of client code. This mismatch often occurs when integrating components developed independently, leading to integration barriers that prevent direct collaboration without significant rework. The primary rationale for employing the Adapter pattern is to enable the reuse of such incompatible classes without modifying their source code, which may be unavailable, , or risky to alter due to potential impacts on dependent systems. By introducing a wrapper class that translates calls from the client's desired interface to the adaptee's actual interface, the pattern facilitates compatibility while preserving the integrity of both components, thereby promoting modular design and reducing the need for extensive refactoring. In real-world applications, this pattern is driven by needs such as bridging legacy systems with modern s, accommodating varying versions across libraries, or harmonizing domain-specific interfaces in heterogeneous environments.

Usage

Common Scenarios

The Adapter pattern is frequently applied in the integration of legacy systems, where older components with incompatible interfaces must be incorporated into modern s. For instance, adapting outdated database drivers to contemporary Object-Relational Mapping (ORM) frameworks allows developers to leverage existing data stores without extensive refactoring, such as bridging a legacy SQL query to a Hibernate-compatible interface in applications. In scenarios involving third-party library compatibility, the pattern enables the wrapping of vendor-specific APIs to align with an application's expected interface, preventing direct modifications to external . A common example is adapting a parser library to function within an XML-based processing system, where the adapter translates data formats and method calls to ensure seamless . Cross-platform development often relies on the Adapter pattern to bridge UI components across diverse frameworks, facilitating in hybrid applications. For example, in mobile apps, Android View components can be adapted to mimic iOS UIKit equivalents, allowing a shared layer to interact uniformly with platform-specific rendering without duplicating implementation. When dealing with API evolution, the pattern addresses breaking changes in service interfaces by introducing adapters that maintain backward compatibility for client code. This approach avoids full rewrites, as seen in updating client applications to new versions of a by mapping deprecated endpoints to their successors through a translation layer. In domain-specific applications like media players, the Adapter pattern unifies playback interfaces for diverse file formats, converting incompatible media protocols or codecs to a standard . This is exemplified in software that adapts , , and proprietary formats to a common audio engine, enabling a single player class to handle multiple sources without altering the underlying decoders.

Benefits and Drawbacks

The Adapter pattern promotes by enabling the integration of existing classes without requiring modifications to their , thereby preserving the integrity of legacy or third-party components. This isolation of adaptation logic enhances maintainability, as changes to the adapted interfaces do not propagate to the core business logic, aligning with the Single Responsibility Principle. Furthermore, it supports the Open-Closed Principle by allowing new adapters to be introduced for future integrations without altering existing client code, thus increasing system flexibility for evolving requirements such as common integration challenges in heterogeneous environments. Despite these advantages, the pattern introduces indirection through additional layers, which can impose a slight overhead in scenarios involving high-frequency calls due to the extra method invocations. It also increases overall code complexity by necessitating new interfaces and classes, potentially leading to a proliferation of implementations that complicate maintenance efforts. In terms of trade-offs, the Adapter pattern proves particularly valuable for one-time or infrequent integrations in large-scale systems, where it reduces between components and facilitates , but excessive application—such as for minor incompatibilities—may violate the YAGNI principle by adding unnecessary abstraction without proportional benefits, making direct code modifications more suitable for greenfield projects.

Structure

UML Class Diagram

The UML class diagram for the Adapter pattern visually represents the structural relationships that allow a client to interact with an incompatible class through a compatible interface. The diagram features four primary elements: the Target, which is an interface or abstract class defining the expected operations; the Client, which relies on the Target for its interactions; the , which conforms to the Target while bridging to the incompatible class; and the Adaptee, an existing class with its own distinct methods that cannot directly collaborate with the Client. Key relationships are denoted by standard UML notations: a dashed arrow indicates the Client's dependency on the Target; a solid line with a hollow triangle shows the Adapter's realization of the Target interface; and an association line connects the Adapter to the Adaptee, illustrating the translation layer. In this structure, the Adapter wraps the Adaptee, converting Target method calls into corresponding Adaptee operations, thereby enabling seamless integration without modifying the original classes. The diagram accommodates two main variations. In the object adapter form, composition is emphasized through a "has-a" relationship, where the Adapter holds a reference to an Adaptee instance, depicted as an association arrow with a diamond end on the Adapter side. The class adapter variation, applicable in languages supporting , instead uses generalization arrows to show the Adapter inheriting from both the Target and Adaptee, creating an "is-a" extension. Reading the diagram highlights the pattern's wrapping mechanism: starting from the Client's dependency on Target, the flow traces through the Adapter's , which delegates and adapts requests to the Adaptee's methods, underscoring how interface incompatibility is resolved at the structural level.

Object Adapter

The object adapter variant of the Adapter pattern employs composition to establish a "has-a" relationship between the adapter and the adaptee, allowing the adapter to implement the target interface while delegating calls to the adaptee's specific methods. In this approach, the adapter acts as a wrapper that translates the incompatible interface of the adaptee—often a third-party or legacy component—into the expected target interface used by the client. This delegation occurs at runtime, enabling seamless collaboration without altering the original classes. A key advantage of the object adapter is its support for of types through composition, which simulates inheriting from multiple classes without the rigidity of direct , thereby adhering to principles like single responsibility and open-closed. Additionally, it provides runtime flexibility, as the adaptee instance can be swapped dynamically without recompiling the client code, making it suitable for pluggable in evolving systems. This reduces dependencies and avoids potential name conflicts that might arise in other adaptation strategies. In terms of structure, the class typically includes a constructor that accepts a reference to the adaptee instance, storing it as a private field for . For instance, a method such as request() in the adapter would invoke the adaptee's specificRequest() method, potentially performing any necessary conversions or mappings in between. This pattern is particularly preferred in languages lacking support, such as , where composition offers greater flexibility for integrating incompatible components dynamically.

Class Adapter

The class adapter variant of the Adapter pattern employs inheritance to enable a class to conform to a target interface while extending an existing adaptee class, thereby translating method calls without requiring object composition. In this approach, the adapter class subclasses the adaptee to inherit its behavior and simultaneously implements the target interface, overriding the target's methods to delegate to or adapt the adaptee's corresponding operations. This mechanism relies on multiple inheritance, allowing the adapter to directly incorporate and modify the adaptee's functionality as needed. A key advantage of the class adapter is its provision of direct access to the adaptee's protected members and methods, facilitating overrides or custom adaptations that would otherwise require public exposure or composition-based workarounds. Additionally, this variant simplifies implementation in languages that support , such as C++, by avoiding the need for delegation code and enabling the adapter to inherit behaviors from both the target and adaptee seamlessly. As a result, it can more readily adapt not just the adaptee but potentially its subclasses through inherited polymorphism. However, the class adapter's dependence on multiple inheritance renders it infeasible in languages enforcing single class inheritance, such as Java or C#, where the target must be an interface and the adaptee a class, limiting direct subclassing. This tight coupling via inheritance also binds the adapter specifically to the adaptee class, reducing flexibility for adapting multiple or varying adaptees compared to composition-based alternatives. In a typical structure, the adapter class extends the adaptee class and implements the target interface; for instance, it overrides the target's request() method to invoke the adaptee's specificRequest() method, performing any necessary translations or return value adjustments within the override. This compile-time binding contrasts with the runtime flexibility of object adapters using composition.

Variations

Pluggable Adapter

The pluggable adapter is a runtime variant of the object adapter pattern that incorporates a or registry mechanism to dynamically load and instantiate adapters, facilitating plug-and-play integration without requiring recompilation of the client code. This approach leverages techniques such as reflection or to resolve and adapt to varying target interfaces at execution time, allowing systems to accommodate multiple incompatible components seamlessly. The primary motivation for pluggable adapters arises from the limitations of compile-time binding in traditional adapters, where adaptations are fixed and require code modifications to switch implementations. By enabling runtime configuration—such as through XML files or annotation-based declarations—pluggable adapters permit flexible switching between different adaptees based on environmental needs, enhancing and in evolving systems. This dynamism addresses scenarios where the exact adaptee is unknown until deployment, reducing the need for static dependencies and promoting extensibility. In terms of structure, a pluggable adapter typically features an adapter factory responsible for creating instances of concrete adapters based on the detected type or configuration parameters. The factory employs runtime introspection, such as Java's reflection API to identify adaptee classes and methods, or dependency injection containers to resolve and inject the appropriate adapter implementation. Common operations are implemented concretely in a base target class, while abstract methods in the adapter allow subclasses to provide type-specific adaptations, ensuring all plugged adapters conform to a unified interface. Applications of pluggable adapters are prominent in enterprise frameworks, such as Spring Integration, where channel adapters serve as pluggable endpoints connecting message channels to diverse external systems like databases, file systems, or messaging queues. These adapters are resolved at runtime using beans, such as ConsumerEndpointFactoryBean, which dynamically configure handlers and channels based on bean definitions or annotations, supporting varying data sources without altering core application logic. This pattern's adoption in such systems underscores its role in building scalable, integration-heavy architectures.

Two-Way Adapter

The two-way adapter extends the standard Adapter pattern to support bidirectional interface conversions, enabling an adaptee to invoke methods on the target interface and vice versa through the same adapter instance. This variation allows mutual compatibility between two classes with incompatible interfaces, where the adapter implements both the target and adaptee interfaces, facilitating transparent communication in either direction. In terms of mechanism, the two-way adapter employs dual delegation: when a client calls a method on the target's interface, the adapter translates and forwards it to the adaptee; conversely, when the adaptee needs to access target functionality, it delegates back through the adapter, which maps the call appropriately to avoid . This bidirectional translation ensures that both parties can operate as if they share a , often requiring the adapter to handle data format conversions or parameter adjustments in both flows. For instance, in integrating a graphical editor framework like Unidraw with a GUI toolkit, the adapter implements both the editor's view interface and the toolkit's interface, forwarding calls bidirectionally to enable seamless interaction. (Note: Sourcemaking references GoF concepts) Common use cases for two-way adapters include bridging bidirectional protocols or data exchanges, such as converting between XML and formats in a system integrating third-party libraries, where requests and responses must flow mutually without altering the original components. Another application arises in scenarios requiring two clients to view and interact with the same object differently, such as adapting enumeration interfaces like Java's to legacy tokenizers, allowing iteration in both directions without infinite recursion. Implementing a two-way adapter introduces challenges, including the risk of infinite loops from circular delegations if method mappings are not carefully guarded—such as by using flags or conditional checks to prevent re-entrant calls. Additionally, maintaining bidirectional mappings demands precise to ensure consistency, increasing development complexity compared to unidirectional adapters.

Implementation

General Steps

Applying the Adapter pattern involves a systematic process to integrate incompatible interfaces without modifying existing code. This approach ensures that clients can interact with legacy or third-party components seamlessly, promoting reusability and maintainability in object-oriented designs. The steps outlined below derive from the foundational description in the seminal work on design patterns.
  1. Identify the Target interface and the incompatible Adaptee: Begin by analyzing the client's requirements to define the Target interface, which specifies the operations the client expects. Simultaneously, pinpoint the Adaptee—the existing class or component with an incompatible interface that performs the desired functionality but cannot be used directly by the client. This step ensures clarity on the mismatch that the Adapter will resolve.
  2. Create the Adapter class: Develop an Adapter class that implements or extends the Target interface. For the object adapter variant, the Adapter holds a reference to an instance of the Adaptee (typically injected via constructor). In the class adapter variant, the Adapter inherits from the Adaptee while also implementing the Target. This structure allows the Adapter to act as a bridge between the two.
  3. Implement translation methods in the Adapter: Within the Adapter, override or implement the Target's methods to translate client requests into calls on the Adaptee. This involves mapping parameters, converting data formats, and handling any discrepancies in method signatures or behaviors to ensure compatibility. The focus remains on interface conversion rather than altering the Adaptee's logic.
  4. Inject the Adapter into the client and test for compatibility: Replace direct references to the Adaptee in the client code with instances of the , allowing the client to interact solely through the Target interface. Verify the integration through testing to confirm that requests are correctly translated and responses are appropriately handled, ensuring no disruptions to the overall system.
To maximize effectiveness, adhere to best practices such as keeping the thin by limiting it to conversion logic, avoiding the addition of new rules. Additionally, define the Target as an interface rather than a class to enhance flexibility and allow for multiple adapters without client modifications. These practices align with principles of emphasized in structural .

Language Examples

The Adapter pattern can be implemented in various languages, demonstrating how an adapter class wraps an adaptee to conform to a target interface through . These examples focus on object adapters, where composition enables the translation of method calls without modifying existing classes.Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1994

Java Example

In Java, a representative implementation adapts an advanced media player capable of playing VLC and MP4 files to a simpler media player interface that expects a unified playMusic method. The MediaAdapter class implements the MediaPlayer target interface and delegates to instances of AdvancedMediaPlayer implementations based on the audio type.

java

// Target interface public interface MediaPlayer { void playMusic(String audioType, String fileName); } // Adaptee interface public interface AdvancedMediaPlayer { void playVlcPlayer(String fileName); void playMp4Player(String fileName); } // Concrete adaptee for VLC public class VlcMusicPlayer implements AdvancedMediaPlayer { public void playVlcPlayer(String fileName) { System.out.println("Playing vlc file: " + fileName); } public void playMp4Player(String fileName) { // Do nothing } } // Concrete adaptee for MP4 public class Mp4MusicPlayer implements AdvancedMediaPlayer { public void playVlcPlayer(String fileName) { // Do nothing } public void playMp4Player(String fileName) { System.out.println("Playing mp4 file: " + fileName); } } // Adapter class public class MediaAdapter implements MediaPlayer { public static final String VLC = "vlc"; public static final String MP_4 = "mp4"; private AdvancedMediaPlayer advancedMusicPlayer; public MediaAdapter(String audioType) { if (audioType.equalsIgnoreCase(VLC)) { advancedMusicPlayer = new VlcMusicPlayer(); } else if (audioType.equalsIgnoreCase(MP_4)) { advancedMusicPlayer = new Mp4MusicPlayer(); } } @Override public void playMusic(String audioType, String fileName) { if (audioType.equalsIgnoreCase(VLC)) { advancedMusicPlayer.playVlcPlayer(fileName); } else if (audioType.equalsIgnoreCase(MP_4)) { advancedMusicPlayer.playMp4Player(fileName); } } }

// Target interface public interface MediaPlayer { void playMusic(String audioType, String fileName); } // Adaptee interface public interface AdvancedMediaPlayer { void playVlcPlayer(String fileName); void playMp4Player(String fileName); } // Concrete adaptee for VLC public class VlcMusicPlayer implements AdvancedMediaPlayer { public void playVlcPlayer(String fileName) { System.out.println("Playing vlc file: " + fileName); } public void playMp4Player(String fileName) { // Do nothing } } // Concrete adaptee for MP4 public class Mp4MusicPlayer implements AdvancedMediaPlayer { public void playVlcPlayer(String fileName) { // Do nothing } public void playMp4Player(String fileName) { System.out.println("Playing mp4 file: " + fileName); } } // Adapter class public class MediaAdapter implements MediaPlayer { public static final String VLC = "vlc"; public static final String MP_4 = "mp4"; private AdvancedMediaPlayer advancedMusicPlayer; public MediaAdapter(String audioType) { if (audioType.equalsIgnoreCase(VLC)) { advancedMusicPlayer = new VlcMusicPlayer(); } else if (audioType.equalsIgnoreCase(MP_4)) { advancedMusicPlayer = new Mp4MusicPlayer(); } } @Override public void playMusic(String audioType, String fileName) { if (audioType.equalsIgnoreCase(VLC)) { advancedMusicPlayer.playVlcPlayer(fileName); } else if (audioType.equalsIgnoreCase(MP_4)) { advancedMusicPlayer.playMp4Player(fileName); } } }

This setup allows a client to invoke playMusic on the adapter, which internally routes the call to the appropriate adaptee method, enabling compatibility without altering the advanced players.Adapter Design Pattern in Java, Java Development Journal, 2022

Python Example

Python's duck typing allows flexible adaptation without strict interfaces, where the adapter provides the expected methods and delegates to the adaptee. A common illustration adapts a European socket (230V) to a socket interface (110V) for use with an electric , using composition to translate voltage and connections.

python

class EuropeanSocketInterface: def voltage(self): pass def live(self): pass def neutral(self): pass def earth(self): pass class Socket(EuropeanSocketInterface): def voltage(self): return 230 def live(self): return 1 def neutral(self): return -1 def earth(self): return 0 class USASocketInterface: def voltage(self): pass def live(self): pass def neutral(self): pass class [Adapter](/page/Adapter)(USASocketInterface): __socket = None def __init__(self, socket): [self](/page/Self).__socket = socket def voltage(self): return 110 def live(self): return [self](/page/Self).__socket.live() def neutral(self): return [self](/page/Self).__socket.neutral() class ElectricKettle: __power = None def __init__(self, power): [self](/page/Self).__power = power def boil(self): if [self](/page/Self).__power.voltage() > 110: print("Kettle on fire!") else: if [self](/page/Self).__power.live() == 1 and [self](/page/Self).__power.neutral() == -1: print("Coffee time!") else: print("No power.") def main(): socket = Socket() adapter = Adapter(socket) kettle = ElectricKettle(adapter) kettle.boil() return 0 if __name__ == "__main__": main()

class EuropeanSocketInterface: def voltage(self): pass def live(self): pass def neutral(self): pass def earth(self): pass class Socket(EuropeanSocketInterface): def voltage(self): return 230 def live(self): return 1 def neutral(self): return -1 def earth(self): return 0 class USASocketInterface: def voltage(self): pass def live(self): pass def neutral(self): pass class [Adapter](/page/Adapter)(USASocketInterface): __socket = None def __init__(self, socket): [self](/page/Self).__socket = socket def voltage(self): return 110 def live(self): return [self](/page/Self).__socket.live() def neutral(self): return [self](/page/Self).__socket.neutral() class ElectricKettle: __power = None def __init__(self, power): [self](/page/Self).__power = power def boil(self): if [self](/page/Self).__power.voltage() > 110: print("Kettle on fire!") else: if [self](/page/Self).__power.live() == 1 and [self](/page/Self).__power.neutral() == -1: print("Coffee time!") else: print("No power.") def main(): socket = Socket() adapter = Adapter(socket) kettle = ElectricKettle(adapter) kettle.boil() return 0 if __name__ == "__main__": main()

Here, the Adapter wraps the Socket instance and translates the USA socket calls, adjusting voltage while delegating live and neutral, leveraging Python's dynamic nature for seamless integration.Python Design Patterns - Adapter, TutorialsPoint

C# Example

In C#, the Adapter pattern uses interfaces and composition to wrap an adaptee's specific request into the target's expected request. The adapter holds a private reference to the adaptee and overrides the target method to delegate and reformat the response.

csharp

// Target interface public interface ITarget { string Request(); } // Adaptee public class Adaptee { public string SpecificRequest() { return "Specific request."; } } // Adapter public class Adapter : ITarget { private readonly Adaptee _adaptee; public Adapter(Adaptee adaptee) { this._adaptee = adaptee; } public string Request() { return $"This is '{_adaptee.SpecificRequest()}'"; } }

// Target interface public interface ITarget { string Request(); } // Adaptee public class Adaptee { public string SpecificRequest() { return "Specific request."; } } // Adapter public class Adapter : ITarget { private readonly Adaptee _adaptee; public Adapter(Adaptee adaptee) { this._adaptee = adaptee; } public string Request() { return $"This is '{_adaptee.SpecificRequest()}'"; } }

This structure ensures the client interacts solely with ITarget.Request(), while the adapter translates it to the adaptee's incompatible method, maintaining loose coupling.Adapter in C#, Refactoring.Guru, accessed 2025 In all cases, the key pattern is delegation via a private field in the adapter, where target methods invoke corresponding adaptee methods, often with parameter or return value translation to bridge incompatibilities.Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1994

Comparisons

With Bridge Pattern

The Adapter pattern and the Bridge pattern are both structural design patterns that facilitate composition between classes, but they address distinct concerns in interface management. The Adapter pattern converts the interface of an existing class into another interface that a client expects, primarily to enable compatibility with legacy or incompatible components after the initial design phase has been completed. This retrofitting approach allows developers to integrate third-party or pre-existing code without altering its source, focusing on one-time mismatches between interfaces. In contrast, the Bridge pattern decouples an from its , allowing the two to vary independently through composition, typically decided at time to support future extensibility. By establishing separate hierarchies for abstractions and implementations, Bridge enables multiple refinements of the abstraction to work with various implementations without tight , anticipating variations in both dimensions from the outset. Key differences lie in their timing, intent, and scope: the pattern reacts to existing incompatibilities by wrapping a single adaptee to match a target interface, often for short-term integration, whereas the Bridge pattern proactively structures the system to handle ongoing evolution in s and implementations across multiple classes. For instance, Adapter might be used to wrap a legacy logging library to fit a modern application's interface, resolving a specific mismatch, while Bridge could separate a (e.g., circles, squares) from drawing APIs (e.g., vector or raster), allowing independent extensions like new shapes or rendering methods. Developers should choose the Adapter pattern for integrating legacy systems or external libraries where interfaces are already fixed and modification is undesirable, such as adapting an old to a new platform. Conversely, the Bridge pattern is preferable for forward-compatible designs involving hierarchies that may evolve, like a user interface over varying platforms (e.g., desktop vs. mobile rendering engines), ensuring scalability without redesign.

With Facade Pattern

The Adapter pattern and the Facade pattern are both structural design patterns that promote loose coupling through object composition, yet they address fundamentally different challenges in interface management. The Adapter pattern enables compatibility between two specific classes or components that have incompatible interfaces by translating calls from one to the other, without altering the underlying objects or simplifying their internal complexity. In essence, it acts as a bridge for peer-level collaboration where direct integration is impossible due to mismatched method signatures or structures. By contrast, the Facade pattern introduces a unified, high-level interface to a complex subsystem—such as a library or framework—hiding intricate interactions among multiple components behind simpler operations, thereby reducing the client's cognitive load without resolving interface incompatibilities. A primary distinction is in their scope and focus: Adapters typically encapsulate a single adaptee object to conform to an expected interface, emphasizing conversion over simplification. Facades, however, orchestrate an entire subsystem of interconnected objects, providing entry points that abstract away details like sequencing or error handling, making the subsystem appear as a cohesive unit. Both patterns employ to forward requests, but an is chosen when interfaces differ fundamentally, while a Facade is appropriate when the issue stems from subsystem intricacy rather than mismatch. For instance, consider integrating a legacy with an platform: an would map the platform's expected calls (e.g., processPayment(amount, card)) to the gateway's differing format (e.g., charge(cardDetails, total)), enabling seamless collaboration without simplifying the gateway's logic. In comparison, a Facade for a home theater setup might offer a straightforward watchMovie([film](/page/Film)) method that internally delegates to the , , , and tuner, concealing the subsystem's coordinated complexity from the user.

References

Add your contribution
Related Hubs
User Avatar
No comments yet.