π 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
More Details:
Get all articles related to system design
Hastag: SystemDesignWithZeeshanAli
Git: https://github.com/ZeeshanAli-0704/SystemDesignWithZeeshanAli
Top comments (0)