Java设计模式:适配器模式深度解析与实战应用
摘要
适配器模式(Adapter Pattern)是结构型设计模式中的"和事佬",它能让原本不兼容的接口协同工作。本文将全面剖析适配器模式的核心概念、实现方式、应用场景以及实际案例,通过丰富的Java代码示例展示如何解决接口不兼容问题,并分析其与相关模式的区别与组合应用。
一、适配器模式概述
适配器模式就像现实世界中的电源适配器,它充当两个不兼容接口之间的桥梁,主要解决以下问题:
- 接口不匹配:已有功能与客户需求接口不一致
- 复用旧代码:需要复用现有类但其接口不符合要求
- 统一接口:创建可复用类以与其他不相关类协作
适配器模式有三种典型实现方式:
- 类适配器(通过继承实现)
- 对象适配器(通过组合实现)
- 接口适配器(缺省适配器)
二、适配器模式的结构
核心角色组成
| 角色 | 职责 | 典型实现 |
|---|---|---|
| Target(目标接口) | 客户端期望的接口 | 抽象类或接口 |
| Adaptee(适配者) | 需要被适配的现有类 | 已存在的类 |
| Adapter(适配器) | 将Adaptee接口转换为Target接口 | 继承或组合Adaptee |
UML类图对比
类适配器
[Target] <|-- [Adapter]
[Adapter] --> [Adaptee]
对象适配器
[Target] <|-- [Adapter]
[Adapter] --> [Adaptee]
三、适配器模式的实现方式
1. 类适配器(继承方式)
// 目标接口(期望的接口)
interface MediaPlayer {
void play(String audioType, String fileName);
}
// 被适配者(已存在的类)
class AdvancedMediaPlayer {
public void playVlc(String fileName) {
System.out.println("Playing vlc file: " + fileName);
}
public void playMp4(String fileName) {
System.out.println("Playing mp4 file: " + fileName);
}
}
// 适配器(继承被适配者)
class MediaAdapter extends AdvancedMediaPlayer implements MediaPlayer {
@Override
public void play(String audioType, String fileName) {
if(audioType.equalsIgnoreCase("vlc")) {
playVlc(fileName);
} else if(audioType.equalsIgnoreCase("mp4")) {
playMp4(fileName);
}
}
}
// 客户端代码
public class AudioPlayer {
public static void main(String[] args) {
MediaPlayer player = new MediaAdapter();
player.play("mp4", "movie.mp4");
player.play("vlc", "song.vlc");
}
}
2. 对象适配器(组合方式)
// 目标接口
interface EuropeanSocket {
void powerTwoRoundPins();
}
// 被适配者
class AmericanPlug {
public void powerTwoFlatPins() {
System.out.println("Providing power through two flat pins");
}
}
// 适配器
class SocketAdapter implements EuropeanSocket {
private AmericanPlug plug;
public SocketAdapter(AmericanPlug plug) {
this.plug = plug;
}
@Override
public void powerTwoRoundPins() {
System.out.println("Adapter converting round pins to flat pins");
plug.powerTwoFlatPins();
}
}
// 客户端代码
public class Traveler {
public static void main(String[] args) {
AmericanPlug myPlug = new AmericanPlug();
EuropeanSocket socket = new SocketAdapter(myPlug);
socket.powerTwoRoundPins();
}
}
3. 接口适配器(缺省适配器)
// 目标接口(包含多个方法)
interface FileOperations {
void read();
void write();
void delete();
void copy();
}
// 缺省适配器(空实现)
abstract class FileOperationsAdapter implements FileOperations {
public void read() {}
public void write() {}
public void delete() {}
public void copy() {}
}
// 具体实现(只需覆盖需要的方法)
class ReadOnlyFile extends FileOperationsAdapter {
@Override
public void read() {
System.out.println("Reading file content");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
FileOperations file = new ReadOnlyFile();
file.read(); // 正常工作
file.write(); // 空操作,不报错
}
}
四、适配器模式的应用场景
- 系统升级兼容:新系统需要复用旧系统功能
- 第三方库集成:使用不兼容接口的第三方库
- 统一多个类接口:多个相似功能类需要统一接口
- 跨平台开发:不同平台API的适配
- 测试驱动开发:创建测试替身(Mock对象)
实际应用案例
- Java I/O中的适配器
// InputStreamReader是字节流到字符流的适配器
InputStream is = new FileInputStream("text.txt");
Reader reader = new InputStreamReader(is); // 适配器
- Spring MVC中的HandlerAdapter
// 统一处理不同类型的控制器
public interface HandlerAdapter {
boolean supports(Object handler);
ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception;
}
- JDBC驱动适配
// 不同数据库驱动实现JDBC统一接口
Connection conn = DriverManager.getConnection(url);
// 实际获得的是数据库厂商提供的适配器实现
五、适配器模式的优缺点
优点:
- 解耦:将目标类和适配者类解耦
- 复用:可以复用现有类,无需修改原有代码
- 灵活:可以适配多个不同的适配者
- 符合开闭原则:可以引入新适配器而不破坏现有代码
缺点:
- 过度使用复杂:过多使用会使系统变得凌乱
- 继承限制:类适配器需要多继承支持(Java不直接支持)
- 性能开销:额外的间接调用会带来轻微性能损失
六、模式对比:适配器 vs 装饰器 vs 代理
| 对比维度 | 适配器模式 | 装饰器模式 | 代理模式 |
|---|---|---|---|
| 目的 | 转换接口 | 增强功能 | 控制访问 |
| 关系 | 事后补救 | 透明增强 | 代表本体 |
| 实现方式 | 继承/组合 | 组合 | 继承/组合 |
| 调用者感知 | 知道适配器 | 不知道装饰器 | 可能知道代理 |
| 典型应用 | 接口转换 | 动态添加功能 | 延迟加载、访问控制 |
七、最佳实践与注意事项
- 优先使用组合:对象适配器比类适配器更灵活
- 接口最小化:保持目标接口尽可能小
- 文档完善:明确标注适配器的转换规则
- 避免过度使用:不要用适配器掩盖设计问题
- 性能考量:高频调用的接口慎用适配器
// 性能优化的适配器示例
class CachingAdapter implements Target {
private Adaptee adaptee;
private Map<String, Object> cache = new HashMap<>();
public CachingAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public Object request(String key) {
if(cache.containsKey(key)) {
return cache.get(key);
}
Object result = adaptee.specificRequest(key);
cache.put(key, result);
return result;
}
}
八、高级应用与变体
1. 双向适配器
// 双向适配器同时实现两个接口
class BiDirectionalAdapter implements NewSystem, OldSystem {
private NewSystem newSystem;
private OldSystem oldSystem;
public BiDirectionalAdapter(NewSystem newSystem, OldSystem oldSystem) {
this.newSystem = newSystem;
this.oldSystem = oldSystem;
}
// 将新系统接口转为旧系统
public void oldMethod() {
newSystem.newMethod();
}
// 将旧系统接口转为新系统
public void newMethod() {
oldSystem.oldMethod();
}
}
2. 自适应适配器(运行时决策)
class SmartAdapter implements Target {
private Object adaptee;
public SmartAdapter(Object adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
// 运行时根据adaptee类型决定适配方式
if(adaptee instanceof AdapteeA) {
((AdapteeA)adaptee).specificRequestA();
} else if(adaptee instanceof AdapteeB) {
((AdapteeB)adaptee).specificRequestB();
}
}
}
九、与其他模式的组合应用
1. 适配器 + 工厂方法
interface PlayerFactory {
MediaPlayer createPlayer(String type);
}
class PlayerAdapterFactory implements PlayerFactory {
public MediaPlayer createPlayer(String type) {
if(type.equals("mp3")) {
return new Mp3Player();
} else {
AdvancedMediaPlayer amp = new AdvancedMediaPlayer();
return new MediaAdapter(amp);
}
}
}
2. 适配器 + 观察者
class EventAdapter implements Observer {
private EventListener listener;
public EventAdapter(EventListener listener) {
this.listener = listener;
}
@Override
public void update(Observable o, Object arg) {
Event event = convertToEvent(arg);
listener.onEvent(event);
}
private Event convertToEvent(Object arg) {
// 转换逻辑...
}
}
十、实战案例:支付系统集成
// 目标接口
interface PaymentProcessor {
void processPayment(double amount, String currency);
}
// 被适配者A(PayPal SDK)
class PayPal {
public void sendPayment(double amount) {
System.out.println("PayPal: Sending $" + amount);
}
}
// 被适配者B(Stripe SDK)
class Stripe {
public void makePayment(int cents, String currencyCode) {
System.out.println("Stripe: Charging " + cents + " " + currencyCode);
}
}
// 适配器A
class PayPalAdapter implements PaymentProcessor {
private PayPal paypal;
public PayPalAdapter(PayPal paypal) {
this.paypal = paypal;
}
@Override
public void processPayment(double amount, String currency) {
if(!"USD".equals(currency)) {
throw new UnsupportedOperationException("PayPal only supports USD");
}
paypal.sendPayment(amount);
}
}
// 适配器B
class StripeAdapter implements PaymentProcessor {
private Stripe stripe;
public StripeAdapter(Stripe stripe) {
this.stripe = stripe;
}
@Override
public void processPayment(double amount, String currency) {
int cents = (int)(amount * 100);
stripe.makePayment(cents, currency);
}
}
// 客户端代码
public class PaymentService {
private PaymentProcessor processor;
public PaymentService(PaymentProcessor processor) {
this.processor = processor;
}
public void processOrder(Order order) {
processor.processPayment(order.getAmount(), order.getCurrency());
}
public static void main(String[] args) {
// 使用PayPal
PaymentProcessor paypal = new PayPalAdapter(new PayPal());
PaymentService service1 = new PaymentService(paypal);
service1.processOrder(new Order(100.0, "USD"));
// 使用Stripe
PaymentProcessor stripe = new StripeAdapter(new Stripe());
PaymentService service2 = new PaymentService(stripe);
service2.processOrder(new Order(85.50, "EUR"));
}
}
总结
适配器模式是解决接口不兼容问题的利器,它通过转换接口的方式让原本无法一起工作的类能够协同工作。在实际开发中,适配器模式常用于系统升级、第三方库集成和跨平台开发等场景。正确使用适配器模式可以显著提高代码的复用性和系统的扩展性,但需要注意不要滥用适配器来掩盖糟糕的设计。理解各种适配器变体及其适用场景,能够帮助我们在面对接口不兼容问题时做出更合理的设计决策。
















