1. 适配器模式的核心价值解析
在Java开发中,我们经常会遇到接口不兼容的问题。想象这样一个场景:你从欧洲带回一个三脚插头的电器,但国内的插座都是两孔的。这时候你会怎么做?大多数人会选择买一个转换插头——这个转换插头就是现实世界中的"适配器"。在软件设计中,适配器模式(Adapter Pattern)扮演着同样的角色。
适配器模式属于结构型设计模式,它主要解决两个已有接口之间不匹配的问题。通过创建一个中间层(适配器),使得原本由于接口不兼容而不能一起工作的类可以协同工作。这种模式在实际开发中应用非常广泛,特别是在系统升级改造、第三方库集成等场景下。
重要提示:适配器模式不是用来解决设计初期接口定义问题的,而是在后期集成已有组件时的兼容性解决方案。
2. 适配器模式的两种实现方式
2.1 类适配器(继承方式)
类适配器通过继承被适配者来实现适配功能。这种方式在Java中需要用到多重继承的特性,但由于Java不支持多重继承,我们通常通过继承被适配类并实现目标接口来实现。
// 被适配的类 class EuropeanSocket { public void supplyPower() { System.out.println("提供220V电力"); } } // 目标接口 interface ChinaSocket { void providePower(); } // 适配器类 class SocketAdapter extends EuropeanSocket implements ChinaSocket { @Override public void providePower() { super.supplyPower(); System.out.println("已转换为中国标准电压"); } }这种实现方式的优点是:
- 可以直接重写被适配类的方法
- 适配器类与被适配类是is-a关系,耦合度较高
缺点是:
- Java单继承的限制导致灵活性不足
- 对适配器类的修改会影响所有使用该适配器的客户端
2.2 对象适配器(组合方式)
对象适配器通过组合被适配者的实例来实现适配功能,这是更推荐的实现方式。
// 被适配的类 class EuropeanSocket { public void supplyPower() { System.out.println("提供220V电力"); } } // 目标接口 interface ChinaSocket { void providePower(); } // 适配器类 class SocketAdapter implements ChinaSocket { private EuropeanSocket socket; public SocketAdapter(EuropeanSocket socket) { this.socket = socket; } @Override public void providePower() { socket.supplyPower(); System.out.println("已转换为中国标准电压"); } }对象适配器的优势在于:
- 更符合组合优于继承的原则
- 一个适配器可以适配多个不同的被适配者
- 可以在运行时动态切换被适配对象
- 对适配器类的修改不会影响被适配类
3. 适配器模式的实际应用场景
3.1 遗留系统集成
在企业应用中,经常需要将新系统与遗留系统集成。假设我们有一个新的支付接口:
interface NewPaymentGateway { void processPayment(double amount); }但遗留系统使用的是旧接口:
class LegacyPaymentSystem { public void makePayment(String currency, int dollars, int cents) { // 旧版支付逻辑 } }我们可以创建一个适配器来桥接这两个系统:
class PaymentAdapter implements NewPaymentGateway { private LegacyPaymentSystem legacySystem; public PaymentAdapter(LegacyPaymentSystem system) { this.legacySystem = system; } @Override public void processPayment(double amount) { int dollars = (int) amount; int cents = (int) ((amount - dollars) * 100); legacySystem.makePayment("USD", dollars, cents); } }3.2 第三方库适配
当引入第三方库时,其API可能与我们系统的接口规范不一致。例如,我们系统使用统一的日志接口:
interface Logger { void log(String level, String message); }但引入的第三方日志库可能是这样的:
class ThirdPartyLogger { public void debug(String msg) { /*...*/ } public void info(String msg) { /*...*/ } public void error(String msg) { /*...*/ } }适配器实现如下:
class LoggerAdapter implements Logger { private ThirdPartyLogger thirdPartyLogger; public LoggerAdapter(ThirdPartyLogger logger) { this.thirdPartyLogger = logger; } @Override public void log(String level, String message) { switch(level.toLowerCase()) { case "debug": thirdPartyLogger.debug(message); break; case "info": thirdPartyLogger.info(message); break; case "error": thirdPartyLogger.error(message); break; default: thirdPartyLogger.info(message); } } }3.3 数据格式转换
另一个常见场景是不同数据格式之间的转换。例如,系统内部使用XML,但外部API返回JSON:
interface XmlProcessor { void process(XML xml); } class JsonApi { public JSON getData() { // 从API获取JSON数据 } } class JsonToXmlAdapter implements XmlProcessor { private JsonApi jsonApi; public JsonToXmlAdapter(JsonApi api) { this.jsonApi = api; } @Override public void process(XML xml) { JSON json = jsonApi.getData(); XML converted = convertJsonToXml(json); // 处理转换后的XML } private XML convertJsonToXml(JSON json) { // 转换逻辑 } }4. 适配器模式的进阶应用与优化
4.1 双向适配器
在某些场景下,我们需要实现两个接口之间的双向适配。这时可以创建一个同时实现两个接口的适配器:
interface EuropeanDevice { void plugIn(EuropeanSocket socket); } interface ChinaDevice { void connect(ChinaSocket socket); } class BiDirectionalAdapter implements EuropeanDevice, ChinaDevice { private EuropeanSocket europeanSocket; private ChinaSocket chinaSocket; // 欧洲设备使用中国插座 @Override public void plugIn(EuropeanSocket socket) { this.europeanSocket = socket; ChinaSocket adapter = new ChinaSocket() { @Override public void providePower() { socket.supplyPower(); } }; // 使用适配后的接口 } // 中国设备使用欧洲插座 @Override public void connect(ChinaSocket socket) { this.chinaSocket = socket; EuropeanSocket adapter = new EuropeanSocket() { @Override public void supplyPower() { socket.providePower(); } }; // 使用适配后的接口 } }4.2 默认适配器(缺省适配器)
当目标接口方法过多,而我们只需要部分功能时,可以使用缺省适配器来减少实现负担:
interface ComplexService { void method1(); void method2(); void method3(); // 更多方法... } abstract class DefaultAdapter implements ComplexService { @Override public void method1() {} @Override public void method2() {} @Override public void method3() {} // 默认空实现 } class CustomAdapter extends DefaultAdapter { @Override public void method1() { // 只实现需要的方法 } }4.3 适配器模式与外观模式的区别
初学者常常混淆适配器模式和外观模式,它们的区别主要在于:
| 特性 | 适配器模式 | 外观模式 |
|---|---|---|
| 目的 | 解决接口不兼容问题 | 提供简化接口 |
| 参与者数量 | 通常涉及两个接口 | 可能涉及多个子系统 |
| 透明性 | 客户端知道适配器的存在 | 客户端不知道子系统的存在 |
| 复杂度 | 相对简单 | 可能很复杂 |
| 使用场景 | 集成已有组件 | 简化复杂系统接口 |
5. 适配器模式的最佳实践与陷阱规避
5.1 适配器命名规范
良好的命名习惯能显著提高代码可读性:
- 被适配类:OriginalClass 或 Adaptee
- 目标接口:TargetInterface
- 适配器类:OriginalToTargetAdapter 或 OriginalAdapter
例如:
- JsonToXmlAdapter
- LegacyPaymentAdapter
- EuropeanToChinaSocketAdapter
5.2 性能考量
适配器模式会引入额外的间接层,可能带来轻微的性能开销。在性能敏感的场景下需要注意:
- 避免在循环中频繁创建适配器实例
- 考虑使用对象池管理适配器实例
- 对于简单的适配需求,可以使用静态方法代替完整适配器类
5.3 常见陷阱与解决方案
陷阱1:过度使用适配器
- 现象:系统中出现大量适配器类
- 解决方案:考虑重构接口设计,统一接口规范
陷阱2:适配器链过长
- 现象:A适配B,B适配C,形成长链
- 解决方案:引入中间统一接口,打破长链
陷阱3:忽略线程安全
- 现象:共享适配器实例在多线程环境下出现问题
- 解决方案:确保适配器是无状态的,或实现适当的同步机制
陷阱4:隐藏设计问题
- 现象:用适配器掩盖了真正的接口设计缺陷
- 解决方案:评估是否应该重构底层设计而非添加适配器
5.4 单元测试策略
适配器类的测试应该关注:
- 接口转换的正确性
- 异常情况的处理
- 性能基准测试(如果需要)
示例测试用例:
@Test public void testPaymentAdapter() { LegacyPaymentSystem legacySystem = new LegacyPaymentSystem(); PaymentAdapter adapter = new PaymentAdapter(legacySystem); // 测试整数金额 adapter.processPayment(100.0); // 验证legacySystem是否正确调用了makePayment("USD", 100, 0) // 测试小数金额 adapter.processPayment(99.99); // 验证legacySystem是否正确调用了makePayment("USD", 99, 99) // 测试边界情况 adapter.processPayment(0.0); adapter.processPayment(1000000.0); }6. 适配器模式在Java生态系统中的应用实例
6.1 Java I/O中的适配器
Java的InputStreamReader就是一个典型的适配器,它将字节流(InputStream)适配为字符流(Reader):
InputStream inputStream = new FileInputStream("file.txt"); Reader reader = new InputStreamReader(inputStream, "UTF-8");这里的InputStreamReader适配了两种不同的流接口。
6.2 集合框架中的适配器
Arrays.asList()方法将数组适配为List接口:
String[] array = {"a", "b", "c"}; List<String> list = Arrays.asList(array);需要注意的是,这种适配是固定大小的,不能添加或删除元素。
6.3 JAXB中的适配器
JAXB (Java Architecture for XML Binding) 提供了XmlAdapter用于自定义XML与Java对象之间的转换:
public class DateAdapter extends XmlAdapter<String, Date> { private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); @Override public Date unmarshal(String v) throws Exception { return dateFormat.parse(v); } @Override public String marshal(Date v) throws Exception { return dateFormat.format(v); } }6.4 Spring中的适配器
Spring MVC使用HandlerAdapter来适配不同类型的控制器:
public interface HandlerAdapter { boolean supports(Object handler); ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; long getLastModified(HttpServletRequest request, Object handler); }不同的适配器实现支持不同的处理器类型,如@Controller注解的类、实现Controller接口的类等。
7. 适配器模式与其他模式的协同
7.1 适配器与工厂模式结合
当需要创建多种适配器时,可以使用工厂模式来管理适配器的创建:
public class AdapterFactory { public static ChinaSocket createSocketAdapter(EuropeanSocket socket) { return new SocketAdapter(socket); } public static Logger createLoggerAdapter(ThirdPartyLogger logger) { return new LoggerAdapter(logger); } // 更多工厂方法... }7.2 适配器与单例模式结合
对于无状态的适配器,可以考虑使用单例模式:
public enum JsonToXmlAdapter { INSTANCE; public XML adapt(JSON json) { // 转换逻辑 } }7.3 适配器与装饰器模式的区别
虽然结构相似,但适配器和装饰器有本质区别:
| 特性 | 适配器模式 | 装饰器模式 |
|---|---|---|
| 目的 | 转换接口 | 增强功能 |
| 关系 | 连接不相关的接口 | 扩展相同接口的功能 |
| 透明度 | 可能改变接口 | 保持接口不变 |
| 使用时机 | 已有组件集成 | 运行时功能扩展 |
| 典型应用 | 集成第三方库 | IO流装饰、UI组件装饰 |
8. 现代Java中的适配器模式演进
8.1 使用Lambda表达式简化适配器
对于函数式接口,可以使用Lambda表达式作为轻量级适配器:
interface OldService { String execute(String input); } interface NewService { String process(String text); } class ServiceAdapter { static NewService adapt(OldService old) { return text -> old.execute(text); } }8.2 使用Method References
方法引用也可以作为适配器的一种简洁实现:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.forEach(System.out::println); // 方法引用适配了Consumer接口8.3 模块化系统中的适配器
在Java 9+的模块系统中,可以使用服务提供者机制实现模块间的适配:
// 在模块A中 public interface DataProcessor { void process(Data data); } // 在模块B中 public class LegacyProcessor { public void handle(LegacyData data) { // 旧处理逻辑 } } // 适配器模块 public class ProcessorAdapter implements DataProcessor { private final LegacyProcessor legacy; public ProcessorAdapter(LegacyProcessor legacy) { this.legacy = legacy; } @Override public void process(Data data) { LegacyData legacyData = convert(data); legacy.handle(legacyData); } private LegacyData convert(Data data) { // 转换逻辑 } }9. 适配器模式的替代方案评估
虽然适配器模式很实用,但在某些场景下可能有更好的替代方案:
9.1 重构统一接口
如果可能,直接重构接口可能是更彻底的解决方案:
// 重构前 interface OldInterface { /*...*/ } interface NewInterface { /*...*/ } class Adapter implements NewInterface { /*...*/ } // 重构后 interface UnifiedInterface { /*...*/ } class OldImpl implements UnifiedInterface { /*...*/ } class NewImpl implements UnifiedInterface { /*...*/ }9.2 使用外观模式
当需要简化复杂子系统接口时,外观模式可能更合适:
class ComplexSystem { void operation1() {} void operation2() {} void operation3() {} } class SimplifiedFacade { private ComplexSystem system; public SimplifiedFacade(ComplexSystem system) { this.system = system; } public void simpleOperation() { system.operation1(); system.operation2(); // 隐藏复杂细节 } }9.3 使用中间语言/格式
在系统集成场景下,引入中间格式可能比适配器更灵活:
interface SystemA { DataA getData(); } interface SystemB { void consume(DataB data); } // 使用中间格式 class IntermediateData { // 通用数据表示 } class SystemAAdapter { IntermediateData adapt(SystemA system) { // 转换逻辑 } } class SystemBAdapter { void send(IntermediateData data, SystemB system) { // 转换逻辑 } }10. 适配器模式实战:支付系统集成案例
让我们通过一个完整的支付系统集成案例来展示适配器模式的实际应用。
10.1 系统现状分析
假设我们有一个电商系统,目前使用PayPal支付:
interface PayPalPayment { void pay(BigDecimal amount, String currency); } class PayPalGateway implements PayPalPayment { @Override public void pay(BigDecimal amount, String currency) { // PayPal支付逻辑 } }现在需要集成新的Stripe支付服务,其接口如下:
class StripePayment { public void charge(int amountInCents, String currencyCode) { // Stripe支付逻辑 } }10.2 设计适配方案
我们需要创建一个适配器,让StripePayment适配PayPalPayment接口:
class StripePaymentAdapter implements PayPalPayment { private final StripePayment stripe; public StripePaymentAdapter(StripePayment stripe) { this.stripe = stripe; } @Override public void pay(BigDecimal amount, String currency) { int cents = amount.multiply(BigDecimal.valueOf(100)).intValue(); stripe.charge(cents, currency); } }10.3 客户端使用
客户端代码可以无缝切换支付方式:
public class PaymentProcessor { private PayPalPayment paymentService; public PaymentProcessor(PayPalPayment paymentService) { this.paymentService = paymentService; } public void processOrder(Order order) { paymentService.pay(order.getAmount(), order.getCurrency()); } } // 使用PayPal PaymentProcessor processor1 = new PaymentProcessor(new PayPalGateway()); // 使用Stripe(通过适配器) PaymentProcessor processor2 = new PaymentProcessor( new StripePaymentAdapter(new StripePayment()) );10.4 支持多种支付方式
我们可以进一步扩展,支持多种支付提供商的动态选择:
class PaymentAdapterFactory { public static PayPalPayment createAdapter(String provider) { switch(provider.toLowerCase()) { case "paypal": return new PayPalGateway(); case "stripe": return new StripePaymentAdapter(new StripePayment()); case "alipay": return new AlipayAdapter(new AlipayService()); default: throw new IllegalArgumentException("Unsupported provider"); } } } // 客户端使用 String selectedProvider = "stripe"; // 可从配置读取 PaymentProcessor processor = new PaymentProcessor( PaymentAdapterFactory.createAdapter(selectedProvider) );10.5 异常处理增强
支付过程中需要处理各种异常情况:
class StripePaymentAdapter implements PayPalPayment { // ...其他代码 @Override public void pay(BigDecimal amount, String currency) { try { int cents = amount.multiply(BigDecimal.valueOf(100)).intValue(); stripe.charge(cents, currency); } catch (StripeException e) { throw new PaymentException("Stripe支付失败", e); } catch (IllegalArgumentException e) { throw new PaymentException("金额转换错误", e); } } }10.6 性能优化考虑
对于高频调用的支付适配器,可以考虑以下优化:
- 对象池管理适配器实例
- 缓存货币转换结果
- 异步执行支付操作
class StripePaymentAdapter implements PayPalPayment { private static final ObjectPool<StripePayment> pool = new ObjectPool<>( () -> new StripePayment(), 5, 10); @Override public void pay(BigDecimal amount, String currency) { StripePayment stripe = pool.borrowObject(); try { int cents = amount.multiply(BigDecimal.valueOf(100)).intValue(); stripe.charge(cents, currency); } finally { pool.returnObject(stripe); } } }11. 适配器模式在微服务架构中的应用
在微服务架构中,适配器模式常用于服务间通信和数据转换。
11.1 服务版本兼容
当服务API升级时,可以使用适配器保持向后兼容:
// V1 客户端接口 interface UserServiceV1 { User getUser(int id); } // V2 服务端接口 interface UserServiceV2 { UserProfile getUserProfile(String uuid); } // 适配器 class UserServiceV1ToV2Adapter implements UserServiceV1 { private final UserServiceV2 v2Service; public UserServiceV1ToV2Adapter(UserServiceV2 v2Service) { this.v2Service = v2Service; } @Override public User getUser(int id) { String uuid = convertIdToUuid(id); UserProfile profile = v2Service.getUserProfile(uuid); return convertProfileToUser(profile); } // 转换方法... }11.2 协议转换
不同微服务可能使用不同的通信协议,如REST、gRPC等:
interface RestUserService { User getUser(String id); } class GrpcUserService { public UserProto getUser(UserRequest request) { // gRPC实现 } } class GrpcToRestAdapter implements RestUserService { private final GrpcUserService grpcService; @Override public User getUser(String id) { UserRequest request = UserRequest.newBuilder() .setUserId(id) .build(); UserProto proto = grpcService.getUser(request); return convertProtoToUser(proto); } // 转换方法... }11.3 数据格式适配
微服务间数据格式可能不同,如JSON、Protocol Buffers等:
class JsonToProtoAdapter { public static UserProto adapt(JsonNode json) { UserProto.Builder builder = UserProto.newBuilder(); if (json.has("id")) { builder.setId(json.get("id").asText()); } if (json.has("name")) { builder.setName(json.get("name").asText()); } // 其他字段... return builder.build(); } }12. 适配器模式的测试策略
12.1 单元测试要点
适配器测试应重点关注:
- 接口转换的正确性
- 异常情况的处理
- 边界条件的覆盖
示例测试类:
class StripePaymentAdapterTest { @Test void testPayWithWholeDollarAmount() { StripePayment mockStripe = mock(StripePayment.class); StripePaymentAdapter adapter = new StripePaymentAdapter(mockStripe); adapter.pay(new BigDecimal("100.00"), "USD"); verify(mockStripe).charge(10000, "USD"); } @Test void testPayWithFractionalAmount() { StripePayment mockStripe = mock(StripePayment.class); StripePaymentAdapter adapter = new StripePaymentAdapter(mockStripe); adapter.pay(new BigDecimal("99.99"), "USD"); verify(mockStripe).charge(9999, "USD"); } @Test void testPayWithZeroAmount() { StripePayment mockStripe = mock(StripePayment.class); StripePaymentAdapter adapter = new StripePaymentAdapter(mockStripe); assertThrows(IllegalArgumentException.class, () -> { adapter.pay(BigDecimal.ZERO, "USD"); }); } }12.2 集成测试策略
集成测试应验证适配器在真实环境中的行为:
class PaymentAdapterIntegrationTest { @Test void testRealPaymentIntegration() { StripePayment stripe = new StripePayment(API_KEY); PayPalPayment adapter = new StripePaymentAdapter(stripe); // 使用测试信用卡 adapter.pay(new BigDecimal("1.00"), "USD"); // 验证支付是否成功(可能需要查询Stripe API) } }12.3 性能测试考虑
对于高频使用的适配器,需要进行性能测试:
class AdapterPerformanceTest { @Test void testAdapterThroughput() { StripePayment stripe = new StripePayment(API_KEY); PayPalPayment adapter = new StripePaymentAdapter(stripe); long start = System.currentTimeMillis(); for (int i = 0; i < 1000; i++) { adapter.pay(new BigDecimal("1.00"), "USD"); } long duration = System.currentTimeMillis() - start; assertTrue(duration < 1000, "1000次支付应在1秒内完成"); } }13. 适配器模式的演进与未来趋势
13.1 自动适配器生成
现代IDE和工具开始支持自动生成适配器代码。例如,IntelliJ IDEA可以通过"Extract Adapter"重构自动创建适配器类。
13.2 动态适配器
使用动态代理或字节码生成技术创建运行时适配器:
public class DynamicAdapter { public static <T> T create(Object adaptee, Class<T> targetInterface) { return (T) Proxy.newProxyInstance( targetInterface.getClassLoader(), new Class<?>[] { targetInterface }, (proxy, method, args) -> { // 动态查找并调用adaptee上的对应方法 Method adapteeMethod = findCorrespondingMethod(method, adaptee); return adapteeMethod.invoke(adaptee, args); }); } private static Method findCorrespondingMethod(Method targetMethod, Object adaptee) { // 实现方法匹配逻辑 } } // 使用示例 StripePayment stripe = new StripePayment(); PayPalPayment adapter = DynamicAdapter.create(stripe, PayPalPayment.class);13.3 函数式适配器
Java 8引入的函数式接口为适配器模式提供了新的实现方式:
@FunctionalInterface interface AdapterFunction<T, R> { R adapt(T source); } class FunctionalAdapters { public static final AdapterFunction<BigDecimal, Integer> DOLLARS_TO_CENTS = amount -> amount.multiply(BigDecimal.valueOf(100)).intValue(); public static final AdapterFunction<String, Currency> STRING_TO_CURRENCY = Currency::getInstance; } // 使用示例 int cents = FunctionalAdapters.DOLLARS_TO_CENTS.adapt(new BigDecimal("1.99"));14. 适配器模式的反模式与误用
14.1 适配器滥用
常见滥用场景包括:
- 用适配器掩盖糟糕的接口设计
- 创建不必要的适配层增加系统复杂度
- 过度设计未来可能需要的适配点
14.2 适配器泄漏
适配器实现暴露了被适配者的内部细节:
// 不好的实现:暴露了Stripe的异常 class StripePaymentAdapter implements PayPalPayment { @Override public void pay(BigDecimal amount, String currency) throws StripeException { // ... } } // 好的实现:转换异常 class StripePaymentAdapter implements PayPalPayment { @Override public void pay(BigDecimal amount, String currency) throws PaymentException { try { // ... } catch (StripeException e) { throw new PaymentException(e.getMessage()); } } }14.3 过度适配
为每个小差异都创建适配器会导致"适配器爆炸":
// 过度设计的例子 interface A { void m1(); } interface B { void m2(); } interface C { void m3(); } class AToBAdapter implements B { /*...*/ } class BToCAdapter implements C { /*...*/ } class AToCAdapter implements C { /*...*/ } // 更多适配器...解决方案是识别真正的接口差异,创建更通用的适配方案。
15. 适配器模式的可维护性考量
15.1 文档化适配关系
为每个适配器添加清晰的文档说明:
/** * 适配器:将Stripe支付接口适配到PayPal支付接口 * * 转换规则: * 1. 金额从BigDecimal(美元)转换为int(美分) * 2. 货币代码直接传递 * 3. Stripe异常转换为PaymentException * * 注意:Stripe的最小支付金额为$0.50 */ class StripePaymentAdapter implements PayPalPayment { // 实现... }15.2 适配器版本管理
当被适配接口或目标接口变更时,需要管理适配器版本:
- 为适配器添加版本信息
- 支持多版本适配器共存
- 提供迁移路径
@Deprecated class StripePaymentAdapterV1 implements PayPalPayment { // 旧版实现 } class StripePaymentAdapterV2 implements PayPalPayment { // 新版实现 }15.3 监控与指标
为生产环境中的适配器添加监控:
class MonitoredAdapter implements PayPalPayment { private final PayPalPayment delegate; private final Counter successCounter; private final Counter errorCounter; private final Timer timer; public MonitoredAdapter(PayPalPayment delegate, MeterRegistry registry) { this.delegate = delegate; this.successCounter = registry.counter("adapter.payment.success"); this.errorCounter = registry.counter("adapter.payment.errors"); this.timer = registry.timer("adapter.payment.duration"); } @Override public void pay(BigDecimal amount, String currency) { Timer.Sample sample = Timer.start(); try { delegate.pay(amount, currency); successCounter.increment(); } catch (Exception e) { errorCounter.increment(); throw e; } finally { sample.stop(timer); } } }16. 适配器模式在不同架构中的实现
16.1 分层架构中的适配器
在分层架构中,适配器常用于连接不同层次:
表现层 → [适配器] ← 业务逻辑层 → [适配器] ← 数据访问层例如,将数据库实体适配为业务对象:
class UserEntityAdapter { public static User toDomain(UserEntity entity) { return new User( entity.getId(), entity.getUsername(), // 其他字段转换... ); } public static UserEntity toEntity(User domain) { UserEntity entity = new UserEntity(); entity.setId(domain.getId()); entity.setUsername(domain.getUsername()); // 其他字段转换... return entity; } }16.2 六边形架构中的适配器
六边形架构(Hexagonal Architecture)大量使用适配器概念:
核心业务逻辑 ← [适配器] → 外部系统/UI/数据库端口和适配器实现示例:
// 端口(核心业务定义的接口) interface OrderRepository { void save(Order order); Order findById(String id); } // 适配器:数据库实现 class JpaOrderRepositoryAdapter implements OrderRepository { private final JpaOrderRepository jpaRepository; @Override public void save(Order order) { OrderEntity entity = convertToEntity(order); jpaRepository.save(entity); } // 其他方法... } // 适配器:内存实现(用于测试) class InMemoryOrderRepositoryAdapter implements OrderRepository { private final Map<String, Order> orders = new HashMap<>(); @Override public void save(Order order) { orders.put(order.getId(), order); } // 其他方法... }16.3 清洁架构中的适配器
清洁架构(Clean Architecture)中,适配器负责将外部细节适配到内部接口:
// 业务核心定义的接口 interface DataService { Data getData(); } // 适配器:REST API实现 class RestDataServiceAdapter implements DataService { private final RestClient restClient; @Override public Data getData() { JsonNode json = restClient.get("/api/data"); return convertJsonToData(json); } } // 适配器:gRPC实现 class GrpcDataServiceAdapter implements DataService { private final DataGrpc.DataBlockingStub stub; @Override public Data getData() { DataResponse response = stub.getData(DataRequest.getDefaultInstance()); return convertProtoToData(response); } }17. 适配器模式与领域驱动设计
17.1 防腐层(Anti-Corruption Layer)
在DDD中,防腐层本质上是一个适配器,保护核心领域不受外部系统模型的影响:
class ExternalSystemAdapter { private final ExternalSystemClient client; public DomainModel getDomainData() { ExternalModel external = client.getData(); return convertToDomain(external); } private DomainModel convertToDomain(ExternalModel external) { // 复杂的转换逻辑,确保外部模型不会污染领域 } }17.2 领域事件适配
将外部事件适配为领域事件:
class ExternalEventAdapter { public static DomainEvent toDomain(ExternalEvent external) { switch(external.getType()) { case "ORDER_CREATED": return new OrderCreatedEvent( external.getOrderId(), external.getTimestamp() ); // 其他事件类型... } } }17.3 值对象适配
在不同限界上下文之间适配值对象:
class MoneyAdapter { public static AccountingMoney toAccounting(OrderMoney orderMoney) { return new AccountingMoney( orderMoney.getAmount(), orderMoney.getCurrency(), LocalDate.now() ); } }18. 适配器模式的性能优化技巧
18.1 对象复用
对于无状态适配器,可以复用实例:
public enum CurrencyAdapter { INSTANCE; public String toIsoCode(String symbol) { // 转换逻辑 } } // 使用 String code = CurrencyAdapter.INSTANCE.toIsoCode("$");18.2 缓存转换结果
对于计算密集型的转换操作,可以引入缓存:
class CachedAdapter { private final Map<BigDecimal, Integer> cache = new ConcurrentHashMap<>(); public int toCents(BigDecimal dollars) { return cache.computeIfAbsent(dollars, d -> d.multiply(BigDecimal.valueOf(100)).intValue()); } }18.3 批量适配
对于集合数据的适配,批量处理更高效:
class BatchUserAdapter { public List<User> adaptAll(List<UserEntity> entities) { return entities.stream() .map(this::adapt) .collect(Collectors.toList()); } private User adapt(UserEntity entity) { // 单个转换逻辑 } }18.4 并行处理
对于CPU密集型的适配操作,可以使用并行流:
class ParallelAdapter { public List<Data> adaptAll(List<RawData> rawData) { return rawData.parallelStream() .map(this::adapt) .collect(Collectors.toList()); } private Data adapt(RawData raw) { // 单个转换逻辑 } }19. 适配器模式的安全考量
19.1 输入验证
适配器应对输入数据进行严格验证:
class SafePaymentAdapter implements PayPalPayment { @Override public void pay(BigDecimal amount, String currency) { if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { throw new IllegalArgumentException("金额必须为正数"); }