link: Structural patterns
Adapter Pattern
Overview
Also known as: Wrapper
Abstract
Adapter is a Structural design pattern that allows objects with incompatible interfaces to collaborate.
Content
Intent
Problem
Imagine you’re developing a stock market monitoring app. It fetches stock data from various sources in XML format and presents it to users in visually appealing charts and diagrams. Later on, you decide to enhance the app by integrating a sophisticated third-party analytics library. But here’s the challenge: the analytics library exclusively processes data in JSON format. This presents an incompatibility issue since your app’s data is in XML format.
Challenges:
- Integrating a smart 3rd-party analytics library that only works with JSON data into an app that fetches data in XML format.
- Changing the library to support XML could potentially disrupt existing code and may not be feasible if the library’s source code is inaccessible.
Solution
Enter the Adapter pattern! It’s like a language translator for objects. The Adapter allows objects with incompatible interfaces to work together seamlessly. Here’s how it tackles the problem:
- Creating an Adapter: You develop special adapters that convert XML data into JSON format for each class of the analytics library your app interacts with directly.
- Hiding Complexity: The adapter wraps around the analytics library, shielding your app’s code from the conversion process complexity.
- Seamless Integration: Your app communicates exclusively through these adapters, ensuring smooth interaction with the analytics library.
- Bi-Directional Conversion: In some cases, you can even create two-way adapters that facilitate conversion in both XML-to-JSON and JSON-to-XML directions.
Application in Stock Market App: To resolve the XML-JSON compatibility issue in your stock market app:
- Develop XML-to-JSON adapters for each analytics library class your app interacts with.
- Modify your app’s code to communicate solely through these adapters.
- When a call is made, the adapter translates XML data into JSON format and forwards it to the appropriate analytics library methods.
Summary: By employing the Adapter pattern, you Bridge the gap between incompatible interfaces, ensuring smooth integration of third-party components into your application without compromising existing functionality.
Structure
- Client:
- The Client class encapsulates the existing business logic of the program.
- It interacts with other classes through a specified interface.
- Client Interface:
- Describes a protocol that other classes must adhere to in order to collaborate with the client code.
- Ensures that different implementations can be seamlessly integrated with the client.
- Service:
- Represents a useful class, often a 3rd-party or legacy component.
- However, the client cannot directly utilize this class due to an incompatible interface.
- Adapter:
- The Adapter class acts as a Bridge between the client and the service.
- Implements the client interface while wrapping the service object.
- Receives calls from the client via the adapter interface and translates them into a format compatible with the service object.
- Utilizes object composition principle to achieve compatibility.
- Benefits:
- The client code remains decoupled from the concrete adapter class as long as it interacts with the adapter through the client interface.
- This decoupling enables the introduction of new adapter types without disrupting existing client code.
- Particularly useful when the interface of the service class undergoes changes or replacements, as new adapter classes can be created without modifying the client code.
Applicability
-
Incompatible Interfaces: When you need to use an existing class, but its interface doesn’t match the rest of your code.
- The Adapter pattern facilitates the creation of a middle-layer class, acting as a translator between your code and the incompatible class. This could be a legacy class, a 3rd-party class, or any other class with a non-standard interface.
-
Reusing Subclasses without Common Functionality: When you want to reuse multiple existing subclasses that lack common functionality, which cannot be added to the superclass.
- Extending each subclass to add the missing functionality leads to code duplication across all subclasses, which is undesirable.
Solution:
- Adapter as a Dynamic Feature Enhancer:
- Instead of extending each subclass and duplicating code, employ an adapter class.
- The adapter encapsulates the missing functionality and dynamically adds it to objects lacking these features.
- For this approach to work, the target classes must share a common interface, and the adapter’s field should adhere to that interface.
- This approach bears resemblance to the Decorator pattern but focuses on bridging interface disparities rather than dynamically adding responsibilities.
How to Implement
-
Identify Incompatible Interfaces:
- Ensure you have at least two classes with incompatible interfaces:
- A useful service class (e.g., 3rd-party, legacy, or with many existing dependencies).
- One or more client classes that could benefit from using the service class.
- Ensure you have at least two classes with incompatible interfaces:
-
Declare Client Interface:
- Define the client interface that describes how clients communicate with the service.
- This interface should provide the methods that the client classes need to interact with the service.
-
Create Adapter Class:
- Develop an adapter class that adheres to the client interface.
- Initially, leave all methods empty.
-
Add Service Object Reference:
- Include a field in the adapter class to hold a reference to the service object.
- Typically, initialize this field through the constructor, but you can also pass it to the adapter when calling its methods.
-
Implement Methods:
- One by one, implement all methods of the client interface in the adapter class.
- Delegate most of the actual work to the service object, focusing on interface or data format conversion within the adapter.
-
Utilize Adapter via Client Interface:
- Ensure that clients use the adapter through the client interface.
- This allows for easy adaptation and extension of adapters without impacting existing client code.
Pros and Cons
Advantages
- Single Responsibility Principle:
- Enables separation of interface or data conversion logic from the primary business logic of the program.
- Each adapter class focuses on a specific task, promoting cleaner and more maintainable code.
- Open Closed Principle:
- Facilitates the introduction of new types of adapters without disrupting existing client code.
- Clients interact with adapters through a common interface, allowing for seamless integration of new adapters without modifying client implementations
Disadvantages
- Increased Code Complexity:
- Introducing a set of new interfaces and classes for adapters can increase the overall complexity of the codebase.
- Requires additional effort to manage and maintain the adapter classes alongside the existing code.
- Simplicity vs. Adaptation:
- Sometimes, it may be simpler and more straightforward to modify the service class to align with the rest of the codebase rather than implementing adapters.
- Deciding whether to use adapters involves weighing the benefits of adaptation against the added complexity introduced by adapter classes and interfaces.
Relations with Other Patterns
- Bridge Pattern:
- Bridge is typically designed upfront, allowing parts of an application to be developed independently of each other.
- Adapter, on the other hand, is often used in existing applications to make otherwise incompatible classes work together smoothly.
- Decorator Pattern:
- Proxy Pattern:
- Adapter provides a different interface to the wrapped object, whereas Proxy provides it with the same interface.
- Additionally, Decorator enhances the interface of the object.
- Facade Pattern:
- Bridge Pattern, State Pattern, Strategy Pattern:
- These patterns, along with Adapter, share similar structures based on composition.
- However, they address different problems, each communicating a distinct solution.
- While Bridge focuses on decoupling abstraction from implementation, State manages object state transitions, and Strategy encapsulates interchangeable algorithms.
- Adapter, in contrast, adapts the interface of one object to another.
Examples
This code exemplifies the Adapter pattern, facilitating interaction between components with incompatible interfaces. It involves an analytics library, an adapter for converting XML to JSON, and a stock market application utilizing the adapter to process market data.
Components of the Implementation
IAnalyticsLibrary
(Target Interface): Defines the interface for analytics libraries, including a methodAnalyzeData
to analyze JSON data.
AnalyticsLibrary
(Adaptee): Implements theIAnalyticsLibrary
interface, analyzing JSON data directly.
XmlToJsonAdapter
(Adapter): Converts XML data to JSON format and forwards it to the analytics library. It implementsIAnalyticsLibrary
, accepting an analytics library instance in its constructor.
StockMarketApp
: Represents a stock market application utilizing the analytics library to process market data. It receives XML market data, converts it to JSON using the adapter, and then analyzes the data using the analytics library.
C# Example - GitHub
Server Side
Client Side
Summary
Cheat Sheet
Adapter Pattern Cheat Sheet Purpose:
- Allows objects with incompatible interfaces to collaborate.
- Often used to make existing classes work with others without modifying their source code.
Components:
- Target: The domain-specific interface that the client uses.
- Adapter: Adapts the interface of the Adaptee to the Target interface.
- Adaptee: The existing class that needs adapting.
- Client: Collaborates with objects conforming to the Target interface.
Usage:
- Use when you want to use an existing class, and its interface does not match the one you need.
- Use when you want to create a reusable class that cooperates with unrelated or unforeseen classes, that is, classes that don’t necessarily share interfaces.
Benefits:
- Introduces only one object, and the adapter’s interface can be customized to work with the client.
- Increases transparency of operation.
- Provides a flexible solution to interface compatibility issues.
Common Scenarios:
- Integrating new components into existing systems where interfaces do not match.
- Converting data into various formats depending on user requirements.