Design Patterns 09 – Adapter Pattern: Connecting Incompatible Interfaces
Adapter pattern acts as a translation layer that lets existing classes with mismatched interfaces interoperate without modification, illustrated through a logistics API integration example, with detailed object and class adapter implementations, trade‑off analysis, standard library and Spring examples, and a comparison with decorator and proxy patterns.
Adapter pattern purpose
Adapter converts one interface into another so that existing classes with incompatible signatures can cooperate without modification.
Real‑world scenario: third‑party logistics SDK
System defines a unified logistics interface:
public interface LogisticsService {
String ship(String orderNo, String address);
}Third‑party SfExpressSdk cannot be changed and offers a different method:
public class SfExpressSdk {
public SfResult createOrder(SfRequest request) {
System.out.println("顺丰下单:" + request);
return new SfResult("SF" + System.currentTimeMillis());
}
}Business code wants to call logisticsService.ship(...) but the SDK only provides createOrder(...). The functionality matches (both place a logistics order) while the interface shape does not.
Adapter concept
Target : the interface the client expects ( LogisticsService).
Adaptee : the existing class with an incompatible interface ( SfExpressSdk).
Adapter : implements the target interface and internally delegates to the adaptee, performing parameter and return‑value translation.
Translation path: client → target interface → adapter (translation) → adaptee.
Object adapter (composition)
Adapter holds an instance of the adaptee and translates calls:
public class SfLogisticsAdapter implements LogisticsService {
private final SfExpressSdk sfSdk;
public SfLogisticsAdapter(SfExpressSdk sfSdk) {
this.sfSdk = sfSdk;
}
@Override
public String ship(String orderNo, String address) {
SfRequest request = new SfRequest();
request.setBizOrderNo(orderNo);
request.setReceiverAddr(address);
SfResult result = sfSdk.createOrder(request);
return result.getWaybillNo();
}
}Usage is transparent to business code:
LogisticsService logistics = new SfLogisticsAdapter(new SfExpressSdk());
String waybill = logistics.ship("NO123", "北京市朝阳区");Benefits: business code depends only on LogisticsService, the SDK remains untouched, and adding a new provider only requires another adapter implementation (Open/Closed Principle).
Class adapter (inheritance)
Adapter inherits from the adaptee and implements the target interface:
public class SfLogisticsClassAdapter extends SfExpressSdk implements LogisticsService {
@Override
public String ship(String orderNo, String address) {
SfRequest request = new SfRequest();
request.setBizOrderNo(orderNo);
request.setReceiverAddr(address);
SfResult result = this.createOrder(request);
return result.getWaybillNo();
}
}Limitation: Java allows only single inheritance, so the adapter consumes the sole inheritance slot and cannot adapt a final class. Flexibility is lower than the object adapter.
Comparison of object vs class adapters
Holding method : composition (object) vs inheritance (class).
Flexibility : high – can adapt subclasses or combine multiple adaptees (object) vs low – adapts only one concrete class (class).
Inheritance limitation : none for object adapter; present for class adapter (uses up the only inheritance opportunity).
Can adapt final class : yes (object); no (class).
Recommendation : object adapter is the preferred choice; class adapter is used only when overriding specific behaviour of the adaptee is required.
Adapter examples in standard libraries and frameworks
InputStreamReader: adapts a byte InputStream to a character Reader (object adapter). Arrays.asList(): adapts an array to the List interface.
Various java.io XxxAdapter classes and Swing/AWT event adapters such as MouseAdapter provide empty implementations for selective overriding.
Spring MVC's HandlerAdapter: adapts different controller styles to a unified handling mechanism, allowing the framework to invoke heterogeneous controllers through a common interface.
Distinguishing Adapter from Proxy and Decorator
Proxy : controls access; interface unchanged.
Decorator : dynamically adds functionality; interface unchanged.
Adapter : converts one interface to another; interface changes.
The decisive test is whether the wrapper changes the client‑visible interface. If yes, it is an adapter.
When to use an Adapter
You have a ready‑made class whose functionality fits your needs but whose interface does not match your system.
The class cannot be modified (third‑party library, legacy code, or another team’s module).
You need to unify multiple heterogeneous implementations under a single interface.
Adapters should not be used as a band‑aid for poor internal interface design; excessive adapters indicate that the original abstractions need refactoring.
Summary
Adapter is a “plug” that bridges incompatible interfaces, allowing existing classes to cooperate without modification. It can be implemented as an object adapter (composition, preferred) or a class adapter (inheritance, limited). Standard library instances include InputStreamReader, Arrays.asList, and Spring MVC's HandlerAdapter. The key distinction from proxy and decorator is that adapters change the interface, while the other two keep it unchanged.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
