1. 什么是适配器模式
想象一下你从国外带回来一个欧标插头的电器,但家里的插座都是国标的,这时候你会怎么办?聪明的你肯定会去买一个转换插头。在编程世界里,适配器模式就是这个"转换插头",它能让原本不兼容的接口愉快地合作。
适配器模式(Adapter Pattern)是一种结构型设计模式,它允许将不兼容的接口转换为客户端期望的另一个接口。就像现实中的适配器一样,它充当两个不兼容接口之间的桥梁,使它们能够协同工作而不需要修改各自的源代码。
在Java中,适配器模式特别常见于以下场景:
- 集成第三方库或组件时
- 重构旧系统但需要保持向后兼容
- 需要统一多个不同接口的行为
2. 适配器模式的实现方式
适配器模式主要有两种实现方式:类适配器和对象适配器。让我们用Java代码来具体看看这两种实现。
2.1 类适配器
类适配器使用继承来实现适配。假设我们有一个旧的温度计接口,返回的是华氏温度,但我们需要摄氏温度。
// 技术栈:Java 11
// 旧的温度计接口(华氏度)
interface FahrenheitThermometer {
double getFahrenheitTemperature();
}
// 新的温度计接口(摄氏度)
interface CelsiusThermometer {
double getCelsiusTemperature();
}
// 具体的华氏温度计实现
class OldThermometer implements FahrenheitThermometer {
@Override
public double getFahrenheitTemperature() {
return 98.6; // 正常人体温度(华氏)
}
}
// 类适配器:通过继承实现适配
class ThermometerAdapter extends OldThermometer implements CelsiusThermometer {
@Override
public double getCelsiusTemperature() {
// 将华氏度转换为摄氏度:(F - 32) × 5/9
return (getFahrenheitTemperature() - 32) * 5 / 9;
}
}
// 使用示例
public class ClassAdapterDemo {
public static void main(String[] args) {
CelsiusThermometer thermometer = new ThermometerAdapter();
System.out.println("当前温度: " + thermometer.getCelsiusTemperature() + "°C");
}
}
2.2 对象适配器
对象适配器使用组合而不是继承来实现适配。让我们用同样的温度计例子来演示:
// 技术栈:Java 11
// 对象适配器:通过组合实现适配
class ThermometerObjectAdapter implements CelsiusThermometer {
private FahrenheitThermometer fahrenheitThermometer;
public ThermometerObjectAdapter(FahrenheitThermometer fahrenheitThermometer) {
this.fahrenheitThermometer = fahrenheitThermometer;
}
@Override
public double getCelsiusTemperature() {
// 同样进行温度转换
return (fahrenheitThermometer.getFahrenheitTemperature() - 32) * 5 / 9;
}
}
// 使用示例
public class ObjectAdapterDemo {
public static void main(String[] args) {
FahrenheitThermometer oldThermometer = new OldThermometer();
CelsiusThermometer thermometer = new ThermometerObjectAdapter(oldThermometer);
System.out.println("当前温度: " + thermometer.getCelsiusTemperature() + "°C");
}
}
3. 适配器模式的实际应用示例
让我们看一个更实际的例子:假设我们需要集成一个第三方支付系统,但它的接口与我们系统的支付接口不兼容。
3.1 定义接口
// 技术栈:Java 11
// 我们系统的支付接口
interface OurPaymentSystem {
void pay(BigDecimal amount, String currency);
boolean verifyPayment(String transactionId);
}
// 第三方支付系统的接口
interface ThirdPartyPayment {
void makePayment(double amount);
String getTransactionStatus(String transactionId);
String getPaymentCurrency();
}
3.2 实现适配器
// 第三方支付系统的具体实现
class PayPalPayment implements ThirdPartyPayment {
private String transactionId;
@Override
public void makePayment(double amount) {
this.transactionId = "PAYPAL-" + UUID.randomUUID().toString();
System.out.println("PayPal处理支付: $" + amount);
}
@Override
public String getTransactionStatus(String transactionId) {
return "COMPLETED"; // 模拟总是成功
}
@Override
public String getPaymentCurrency() {
return "USD"; // PayPal只支持美元
}
}
// 支付适配器
class PaymentAdapter implements OurPaymentSystem {
private ThirdPartyPayment thirdPartyPayment;
public PaymentAdapter(ThirdPartyPayment thirdPartyPayment) {
this.thirdPartyPayment = thirdPartyPayment;
}
@Override
public void pay(BigDecimal amount, String currency) {
// 检查货币是否支持
if (!"USD".equals(currency)) {
throw new IllegalArgumentException("只支持USD支付");
}
// 转换金额类型并调用第三方支付
thirdPartyPayment.makePayment(amount.doubleValue());
}
@Override
public boolean verifyPayment(String transactionId) {
String status = thirdPartyPayment.getTransactionStatus(transactionId);
return "COMPLETED".equals(status);
}
}
3.3 使用适配器
public class PaymentDemo {
public static void main(String[] args) {
// 创建第三方支付实例
ThirdPartyPayment paypal = new PayPalPayment();
// 创建适配器
OurPaymentSystem paymentSystem = new PaymentAdapter(paypal);
// 使用我们的接口进行支付
paymentSystem.pay(new BigDecimal("100.00"), "USD");
// 验证支付
boolean verified = paymentSystem.verifyPayment("some-transaction-id");
System.out.println("支付验证结果: " + verified);
}
}
4. 适配器模式与关联技术
适配器模式经常与其他设计模式一起使用或比较。让我们看看它与一些关联技术的区别和联系。
4.1 适配器模式 vs 装饰器模式
虽然两者都使用组合,但目的不同:
- 适配器模式:改变接口以使其兼容
- 装饰器模式:在不改变接口的情况下添加功能
4.2 适配器模式 vs 外观模式
外观模式简化接口,而适配器模式转换接口:
- 适配器模式:将一个接口转换为另一个接口
- 外观模式:为复杂子系统提供一个简化接口
4.3 适配器模式与函数式接口
在Java 8+中,我们可以利用函数式接口和lambda表达式创建轻量级适配器:
// 技术栈:Java 11
// 旧接口
interface LegacyService {
String fetchData(int id);
}
// 新接口
interface ModernService {
Optional<String> retrieveData(String identifier);
}
public class FunctionalAdapterDemo {
public static void main(String[] args) {
LegacyService legacy = id -> "数据-" + id;
// 使用lambda表达式创建适配器
ModernService modern = identifier ->
Optional.ofNullable(legacy.fetchData(Integer.parseInt(identifier)));
Optional<String> result = modern.retrieveData("123");
result.ifPresent(System.out::println);
}
}
5. 适配器模式的应用场景
适配器模式在以下场景中特别有用:
集成第三方库:当你需要使用一个第三方库,但其接口与你的系统不兼容时。
遗留系统现代化:在重构旧系统时,保持新旧系统的兼容性。
接口标准化:当你有多个类实现相似功能但接口不同,需要统一接口时。
测试替身:在单元测试中,创建测试替身(Test Double)来模拟真实对象。
多平台支持:当需要为不同平台提供统一接口时。
6. 适配器模式的技术优缺点
6.1 优点
解耦客户端和被适配者:客户端只与目标接口交互,不知道适配器和被适配者的存在。
复用现有类:可以复用那些接口不兼容但功能合适的现有类。
灵活性:可以在不修改原有代码的情况下引入新的适配器。
符合开闭原则:对扩展开放,对修改关闭。
6.2 缺点
过度使用会导致混乱:如果系统中适配器太多,会增加理解和维护的难度。
性能开销:额外的间接调用会带来轻微的性能开销。
可能掩盖设计问题:有时接口不匹配可能表明更深层次的设计问题,使用适配器可能会掩盖这些问题。
7. 使用适配器模式的注意事项
不要滥用:只有在真正需要接口转换时才使用适配器模式,不要用它来修补糟糕的设计。
保持适配器简单:适配器应该只负责接口转换,不应该包含业务逻辑。
考虑线程安全:如果适配器在多线程环境中使用,确保它是线程安全的。
文档化适配关系:清楚地记录为什么需要适配器以及它转换了什么。
考虑替代方案:有时候重构接口可能是更好的选择,特别是当你有权修改双方代码时。
8. 文章总结
适配器模式是Java开发者的瑞士军刀之一,特别是在集成不同系统或组件时。它就像是一个翻译官,让讲不同"语言"的接口能够相互理解、愉快合作。通过本文的示例,我们看到了适配器模式如何在实际项目中解决接口不兼容的问题。
记住,适配器模式不是万能的,它最适合用于当你无法或不应该修改现有代码的情况。在决定使用适配器模式之前,总是考虑是否有更简单的解决方案,比如直接修改接口(如果你有权限的话)。
最后,适配器模式的美妙之处在于它的简单性和实用性。它不需要复杂的框架或高级的语言特性,只需要一些面向对象设计的基本概念。掌握适配器模式,你将能够更灵活地处理现实世界中不可避免的接口不匹配问题。
评论