π Adapter Design Pattern in Java β A Complete Guide
π Table of Contents
β What is the Adapter Design Pattern?
The Adapter Pattern is a structural design pattern that allows objects with incompatible interfaces to work together by converting one interface into another the client expects.
Intent: Bridge the gap between a new interface and an existing (often legacy) implementation.
This pattern acts as a wrapper around an existing class, exposing a new interface to the client.
π₯ Key Participants
Role | Description |
---|---|
Target | The interface expected by the client. |
Adaptee | The existing class that needs adapting. |
Adapter | Implements the Target interface and translates calls to the Adaptee. |
Client | Uses the Target interface to interact with the system. |
π§ Real world Analogy
Think about a mobile charger adapter. Your laptop may have a USB-C port, but your phone uses Micro-USB. The adapter converts the USB-C output to Micro-USB input so your phone can charge β even though the two interfaces donβt match.
π UML Diagram (Text Format)
+--------------+ | Client | +--------------+ | v +--------------+ +---------------+ | Target |<------+ Adapter | +--------------+ +---------------+ | - adaptee | +---------------+ | +request() | +---------------+ | v +---------------+ | Adaptee | +---------------+ | +specificRequest() | +---------------+
π» Java Example
π Scenario: You have a legacy payment system (OldPaymentGateway
) and want to integrate it with your new PaymentProcessor
interface.
1. Target Interface
public interface PaymentProcessor { void pay(String amount); }
2. Adaptee Class (Existing API)
public class OldPaymentGateway { public void makePayment(String amountInRupees) { System.out.println("Payment made using OldPaymentGateway: βΉ" + amountInRupees); } }
3. Adapter Class
public class PaymentAdapter implements PaymentProcessor { private OldPaymentGateway oldPaymentGateway; public PaymentAdapter(OldPaymentGateway oldPaymentGateway) { this.oldPaymentGateway = oldPaymentGateway; } @Override public void pay(String amount) { // Adapting method call oldPaymentGateway.makePayment(amount); } }
4. Client Code
public class Main { public static void main(String[] args) { OldPaymentGateway oldGateway = new OldPaymentGateway(); PaymentProcessor processor = new PaymentAdapter(oldGateway); processor.pay("2500"); } }
Output:
Payment made using OldPaymentGateway: βΉ2500
π Use Cases in Real World Systems
- Integrating legacy systems with modern interfaces
- Adapting third-party APIs (e.g., wrapping Stripe's API with your own payment layer)
- Making incompatible class libraries work together
- Wrapping classes with different naming conventions or method signatures
- Supporting backward compatibility
β Advantages
- Promotes code reusability by allowing integration of old components
- Improves interoperability between systems
- Provides flexibility in evolving interfaces
- Encourages decoupling between client and implementation
β Disadvantages
- Can increase complexity with too many adapters
- Might introduce runtime overhead with deep adapter chains
- Sometimes hides the true power of the Adaptee
- Tight coupling to Adaptee may remain if not designed carefully
π§ When to Use and Not to Use
β Use When:
- You want to integrate an existing class but its interface doesnβt match
- Youβre using third-party or legacy code
- Youβre refactoring to support a new interface gradually
β Avoid When:
- You can directly modify the existing class
- You donβt control the Adaptee and behavior is unpredictable
- Adapter logic becomes too complex β consider Facade instead
π Object Adapter vs Class Adapter
Type | Description |
---|---|
Object Adapter | Uses composition (holds Adaptee instance). More flexible. |
Class Adapter | Uses inheritance (extends Adaptee). Tightly coupled, limited to single inheritance. |
Java only supports Object Adapters as multiple inheritance is not allowed.
π Comparison with Similar Patterns
Pattern | Purpose |
---|---|
Adapter | Convert one interface to another. |
Decorator | Add behavior dynamically without changing interface. |
Facade | Simplify a complex subsystem with a unified interface. |
Proxy | Provide a surrogate or placeholder. |
β οΈ Best Practices and Pitfalls
β Best Practices:
- Name adapters clearly (e.g.,
PaymentAdapter
,LegacyToNewAdapter
) - Make adapters lightweight
- Favor object composition over inheritance
- Use interfaces to abstract clients from concrete adapters
β Pitfalls:
- Don't create adapter chains (adapter over adapter)
- Avoid adapting too many unrelated interfaces β use Facade instead
- Donβt tightly couple your Adapter to internal logic of Adaptee
π Alternatives
Alternative | Use When |
---|---|
Wrapper/Wrapper Class | Simple alias or name change of methods |
Facade Pattern | When you need to simplify multiple interfaces |
Strategy Pattern | When you need interchangeable algorithms |
Bridge Pattern | To decouple abstraction from implementation |
π Summary
- The Adapter Pattern bridges incompatible interfaces.
- Useful for integrating legacy or third-party APIs.
- Involves Target, Adaptee, Adapter, and Client.
- Java supports Object Adapters (via composition).
- Be cautious of complexity and avoid overusing adapters.
- Best used when refactoring or integrating external libraries.
--
π Explore More Design Patterns in Java
- π Mastering the Singleton Design Pattern in Java β A Complete Guide
- β οΈ Why You Should Avoid Singleton Pattern in Modern Java Projects
- π Factory Design Pattern in Java β A Complete Guide
- π§° Abstract Factory Design Pattern in Java β Complete Guide with Examples
- π§± Builder Design Pattern in Java β A Complete Guide
- π Iterator Design Pattern in Java β Complete Guide
- π Observer Design Pattern in Java β Complete Guide
- π Strategy Design Pattern in Java β A Complete Guide
- π Decorator Design Pattern in Java β Complete Guide
More Details:
Get all articles related to system design
Hastag: SystemDesignWithZeeshanAli
Git: https://github.com/ZeeshanAli-0704/SystemDesignWithZeeshanAli
Top comments (0)