Recent from talks
Nothing was collected or created yet.
Adapter pattern
View on WikipediaIn 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
adapterclass that converts the (incompatible) interface of a class (adaptee) into another interface (target) clients require. - Work through an
adapterto 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]
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 adapterway implements thetargetinterface by delegating to anadapteeobject at run-time (adaptee.specificOperation()). - The
class adapterway implements thetargetinterface by inheriting from anadapteeclass 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.


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]


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:
- Define an intermediary "provider" interface, and write an implementation of that provider interface that wraps the source of the data,
ClassAin 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(); } }
- 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); } }
- Register the
adapterwith a global registry, so that theadaptercan be looked up at runtime:AdapterFactory.getInstance().registerAdapter(ClassA.class, ClassAFormat1Adapter.class, "format1");
- In code, when wishing to transfer data from
ClassAtoClassB, 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() );
- 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");
- And if it is desired to output the data from
ClassAas, say, image data inClass C:Adapter adapter = AdapterFactory.getInstance() .getAdapterFromTo(ClassA.class, ImageProvider.class, "format2"); ImageProvider provider = (ImageProvider) adapter.adapt(classA); classC.setImage(provider.getImage());
- In this way, the use of adapters and providers allows multiple "views" by
ClassBandClassCintoClassAwithout 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]- Adapter Java Design Patterns - Adapter
- Delegation, strongly relevant to the object adapter pattern.
- Dependency inversion principle, which can be thought of as applying the adapter pattern, when the high-level class defines its own (adapter) interface to the low-level module (implemented by an adaptee class).
- Ports and adapters architecture
- Shim
- Wrapper function
- Wrapper library
References
[edit]- ^ a b Freeman, Eric; Freeman, Elisabeth; Sierra, Kathy; Bates, Bert (2004). Head First Design Patterns. O'Reilly Media. p. 244. ISBN 978-0-596-00712-6. OCLC 809772256. Archived from the original (paperback) on 2013-05-04. Retrieved 2013-04-30.
- ^ Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley. pp. 139ff. ISBN 0-201-63361-2.
- ^ "The Adapter design pattern - Problem, Solution, and Applicability". w3sDesign.com. Archived from the original on 2017-08-28. Retrieved 2017-08-12.
- ^ Freeman, Eric; Freeman, Elisabeth; Sierra, Kathy; Bates, Bert (2004). Hendrickson, Mike; Loukides, Mike (eds.). Head First Design Patterns (paperback). Vol. 1. O'Reilly Media. pp. 243, 252, 258, 260. ISBN 978-0-596-00712-6. Retrieved 2012-07-02.
- ^ "The Adapter design pattern - Structure and Collaboration". w3sDesign.com. Archived from the original on 2017-08-28. Retrieved 2017-08-12.
Adapter pattern
View on GrokipediaFundamentals
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.[4] 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.[4] 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.[4] As described by the Gang of Four, 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.[4] The pattern was first formalized in the 1994 book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, known as the Gang of Four.[4]Motivation
The Adapter pattern arises from the common challenge in software development 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.[5] 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, proprietary, 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.[6][5] In real-world applications, this pattern is driven by needs such as bridging legacy systems with modern architectures, accommodating varying API versions across libraries, or harmonizing domain-specific interfaces in heterogeneous environments.[7][5]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 architectures. 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 executor to a Hibernate-compatible interface in Java applications.[8][9] 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 code. A common example is adapting a JSON parser library to function within an XML-based processing system, where the adapter translates data formats and method calls to ensure seamless interoperability.[5][8] Cross-platform development often relies on the Adapter pattern to bridge UI components across diverse frameworks, facilitating code reuse in hybrid applications. For example, in mobile apps, Android View components can be adapted to mimic iOS UIKit equivalents, allowing a shared business logic layer to interact uniformly with platform-specific rendering without duplicating implementation.[10] 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 RESTful API by mapping deprecated endpoints to their successors through a translation layer.[8] 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 input method. This is exemplified in software that adapts MP3, WAV, and proprietary formats to a common audio engine, enabling a single player class to handle multiple sources without altering the underlying decoders.[11]Benefits and Drawbacks
The Adapter pattern promotes code reuse by enabling the integration of existing classes without requiring modifications to their source code, thereby preserving the integrity of legacy or third-party components.[5] 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.[12] 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.[5] Despite these advantages, the pattern introduces indirection through additional layers, which can impose a slight performance overhead in scenarios involving high-frequency calls due to the extra method invocations.[12] It also increases overall code complexity by necessitating new interfaces and classes, potentially leading to a proliferation of adapter implementations that complicate maintenance efforts.[5] In terms of trade-offs, the Adapter pattern proves particularly valuable for one-time or infrequent integrations in large-scale systems, where it reduces coupling between components and facilitates reuse, 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.[12]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 Adapter, 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.[5][13] 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.[5] 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.[5] The class adapter variation, applicable in languages supporting multiple inheritance, 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 implementation, which delegates and adapts requests to the Adaptee's methods, underscoring how interface incompatibility is resolved at the structural level.[13][5]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.[5][14][15] A key advantage of the object adapter is its support for multiple inheritance of types through composition, which simulates inheriting from multiple classes without the rigidity of direct inheritance, 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 adaptations in evolving systems. This loose coupling reduces dependencies and avoids potential name conflicts that might arise in other adaptation strategies.[5][15][14] In terms of structure, the adapter class typically includes a constructor that accepts a reference to the adaptee instance, storing it as a private field for delegation. For instance, a method such asrequest() in the adapter would invoke the adaptee's specificRequest() method, potentially performing any necessary conversions or parameter mappings in between. This pattern is particularly preferred in languages lacking multiple inheritance support, such as Java, where composition offers greater flexibility for integrating incompatible components dynamically.[5][14][15]
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.[5][16] 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 multiple inheritance, 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.[16][5] 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.[5][16] In a typical structure, the adapter class extends the adaptee class and implements the target interface; for instance, it overrides the target'srequest() method to invoke the adaptee's specificRequest() method, performing any necessary parameter translations or return value adjustments within the override. This compile-time binding contrasts with the runtime flexibility of object adapters using composition.[5][16]
Variations
Pluggable Adapter
The pluggable adapter is a runtime variant of the object adapter pattern that incorporates a factory or registry mechanism to dynamically load and instantiate adapters, facilitating plug-and-play integration without requiring recompilation of the client code.[17] This approach leverages techniques such as reflection or dependency injection to resolve and adapt to varying target interfaces at execution time, allowing systems to accommodate multiple incompatible components seamlessly.[18] 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.[19] 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 modularity and maintainability in evolving systems.[20] This dynamism addresses scenarios where the exact adaptee is unknown until deployment, reducing the need for static dependencies and promoting extensibility.[17] 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.[20] 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.[17] 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.[18] 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.[20] These adapters are resolved at runtime using factory 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.[20] This pattern's adoption in such systems underscores its role in building scalable, integration-heavy architectures.[19]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.[5][21] 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 direct coupling. This bidirectional translation ensures that both parties can operate as if they share a common interface, 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 window interface, forwarding calls bidirectionally to enable seamless interaction.[5] (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 JSON 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 Iterator to legacy tokenizers, allowing iteration in both directions without infinite recursion.[5][21] 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 synchronization to ensure consistency, increasing development complexity compared to unidirectional adapters.[22][5]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.- 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.[5][23]
- 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.[5]
- 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.[23][5]
- Inject the Adapter into the client and test for compatibility: Replace direct references to the Adaptee in the client code with instances of the Adapter, 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.
Language Examples
The Adapter pattern can be implemented in various object-oriented programming languages, demonstrating how an adapter class wraps an adaptee to conform to a target interface through delegation. 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, 1994Java 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. TheMediaAdapter class implements the MediaPlayer target interface and delegates to instances of AdvancedMediaPlayer implementations based on the audio type.
// 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);
}
}
}
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 USA socket interface (110V) for use with an electric kettle, using composition to translate voltage and connections.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()
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.// 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()}'";
}
}
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.[24][25] In contrast, the Bridge pattern decouples an abstraction from its implementation, allowing the two to vary independently through composition, typically decided at design time to support future extensibility. By establishing separate hierarchies for abstractions and implementations, Bridge enables multiple refinements of the abstraction to work with various concrete implementations without tight coupling, anticipating variations in both dimensions from the outset.[26][25] Key differences lie in their timing, intent, and scope: the Adapter 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 abstractions 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 shape abstraction (e.g., circles, squares) from drawing APIs (e.g., vector or raster), allowing independent extensions like new shapes or rendering methods.[24][26] 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 payment gateway to a new e-commerce platform. Conversely, the Bridge pattern is preferable for forward-compatible designs involving hierarchies that may evolve, like a user interface abstraction layer over varying platforms (e.g., desktop vs. mobile rendering engines), ensuring scalability without redesign.[24][25]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.[27] 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.[5] In essence, it acts as a bridge for peer-level collaboration where direct integration is impossible due to mismatched method signatures or structures.[8] 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.[28] 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.[5] 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.[28] Both patterns employ delegation to forward requests, but an Adapter is chosen when interfaces differ fundamentally, while a Facade is appropriate when the issue stems from subsystem intricacy rather than mismatch.[29] For instance, consider integrating a legacy payment gateway with an e-commerce platform: an Adapter would map the platform's expected API 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.[8] In comparison, a Facade for a home theater setup might offer a straightforward watchMovie([film](/page/Film)) method that internally delegates to the DVD player, projector, amplifier, and tuner, concealing the subsystem's coordinated complexity from the user.[28]