Java 23 种设计模式从踩坑到精通 | 番外适配器模式 —— 类适配器 vs 对象适配器摘要适配器模式有两种实现方式对象适配器组合和类适配器继承。对象适配器持有被适配者的实例通过委托完成接口转换类适配器直接继承被适配者同时实现目标接口。本文用顺丰快递对接的同一场景展示两种适配器的代码差异帮你彻底理解“组合优于继承”原则在适配器模式中的具体体现。《Java 23 种设计模式从踩坑到精通》开篇系列介绍与目录 正篇适配器模式 Adapter —— 让不兼容的接口也能一起工作 当前番外 · 适配器模式 × 类适配器 vs 对象适配器 返回系列总目录1. 适配器的两种实现方式在之前的设计模式正篇中我们讲解了对象适配器——通过组合持有被适配者实例。实际上适配器模式还有另一种实现类适配器——通过继承被适配者来实现适配。两者的核心目标相同将一个类的接口转换成客户端期望的另一个接口。但实现方式和适用场景有显著差异。假设物流平台有一个统一的ShippingService接口而对接的顺丰 SDK 提供的是SFExpress类方法名和参数完全对不上平台统一接口ShippingService顺丰原有接口SFExpresscreateOrder(orderNo, receiver, address)sfCreate(sfOrderNo, phone, addr)cancelOrder(orderNo)sfCancel(sfOrderNo)trackOrder(orderNo)sfQuery(sfOrderNo)calculateFee(weight, destination)sfGetPrice(kg, city)对象适配器的思路创建一个类实现ShippingService内部持有SFExpress实例在方法中完成参数转换后委托给SFExpress。类适配器的思路创建一个类同时继承SFExpress并实现ShippingService在重写的方法中直接调用父类SFExpress的方法。2. 类适配器 UML3. 完整源码实现3.1 目标接口平台统一的快递服务/** * 目标接口Target * 物流平台期望的统一快递接口 */publicinterfaceShippingService{voidcreateOrder(StringorderNo,Stringreceiver,Stringaddress);voidcancelOrder(StringorderNo);StringtrackOrder(StringorderNo);doublecalculateFee(doubleweight,Stringdestination);}白话这是物流平台“期望”的接口格式——createOrder、trackOrder、calculateFee。所有对接的快递公司都必须适配到这个接口。3.2 被适配者顺丰快递importjava.util.*;/** * 被适配者Adaptee * 顺丰快递的原有接口方法名和参数完全不同 */publicclassSFExpress{publicvoidsfCreate(StringsfOrderNo,Stringphone,Stringaddr){System.out.printf(【顺丰系统】创建订单 | 单号%s | 电话%s | 地址%s\n,sfOrderNo,phone,addr);}publicvoidsfCancel(StringsfOrderNo){System.out.printf(【顺丰系统】取消订单 | 单号%s\n,sfOrderNo);}publicStringsfQuery(StringsfOrderNo){returnString.format(顺丰快递[%s]已揽收运输中,sfOrderNo);}publicdoublesfGetPrice(doublekg,Stringcity){returnkg*12(city.contains(偏远)?15:0);}}白话顺丰的 SDK 用sfCreate、sfQuery、sfGetPrice这样的方法——和平台统一的createOrder、trackOrder、calculateFee完全不同。3.3 类适配器继承 SFExpress 实现 ShippingServiceimportjava.util.*;/** * 类适配器通过继承被适配者 实现目标接口来完成适配 * * 特点 * - 直接继承 SFExpress获得其所有 protected/public 方法 * - 实现 ShippingService对外暴露统一接口 * - 重写方法时内部调用父类即 SFExpress的方法 */publicclassSFExpressClassAdapterextendsSFExpressimplementsShippingService{// 订单号映射平台订单号 - 顺丰单号privateMapString,StringorderMappingnewHashMap();OverridepublicvoidcreateOrder(StringorderNo,Stringreceiver,Stringaddress){// 参数转换StringsfOrderNoSForderNo;StringphoneextractPhone(address);StringaddrextractAddress(address);// 直接调用父类方法继承来的不需要 sfExpress.sfCreate(...)super.sfCreate(sfOrderNo,phone,addr);orderMapping.put(orderNo,sfOrderNo);System.out.printf(✅ 类适配器平台订单[%s] 已映射为顺丰单号[%s]\n,orderNo,sfOrderNo);}OverridepublicvoidcancelOrder(StringorderNo){StringsfOrderNoorderMapping.get(orderNo);if(sfOrderNo!null){super.sfCancel(sfOrderNo);// 直接调用父类方法orderMapping.remove(orderNo);}}OverridepublicStringtrackOrder(StringorderNo){StringsfOrderNoorderMapping.get(orderNo);if(sfOrderNo!null){Stringresultsuper.sfQuery(sfOrderNo);// 直接调用父类方法return[平台统一格式] result.replace(顺丰快递,快递);}return未找到该订单的物流信息;}OverridepublicdoublecalculateFee(doubleweight,Stringdestination){returnsuper.sfGetPrice(weight,destination);// 直接调用父类方法}privateStringextractPhone(Stringaddress){if(address.contains(电话:)){returnaddress.split(电话:)[1].split(;)[0];}return未知;}privateStringextractAddress(Stringaddress){if(address.contains(地址:)){returnaddress.split(地址:)[1];}returnaddress;}}白话类适配器用extends SFExpress继承顺丰的所有方法用implements ShippingService对外提供平台统一接口。重写的createOrder内部直接调用super.sfCreate(...)——因为继承了父类所以可以直接用super调用。3.4 客户端物流平台/** * 客户端物流平台 * 只依赖统一的 ShippingService完全不关心底层适配方式 */publicclassLogisticsPlatform{privateStringname;privateShippingServiceshippingService;publicLogisticsPlatform(Stringname){this.namename;}publicvoidsetShippingService(ShippingServiceservice){this.shippingServiceservice;System.out.println(【name】已接入快递服务service.getClass().getSimpleName());}publicvoidship(StringorderNo,Stringreceiver,Stringaddress){System.out.println(\n.repeat(50));System.out.println(【name】发起发货请求);shippingService.createOrder(orderNo,receiver,address);}publicvoidtrack(StringorderNo){System.out.println(\n.repeat(50));System.out.println(【name】查询物流信息);StringresultshippingService.trackOrder(orderNo);System.out.println(result);}publicstaticvoidmain(String[]args){System.out.println( 类适配器模式演示 \n);LogisticsPlatformplatformnewLogisticsPlatform(智能物流平台);// 直接使用类适配器platform.setShippingService(newSFExpressClassAdapter());platform.ship(ORD-2001,王五,电话:13700137000;地址:北京市朝阳区);platform.track(ORD-2001);}}白话客户端只和ShippingService接口打交道完全不关心底层是类适配器还是对象适配器。3.5 运行结果 类适配器模式演示 【智能物流平台】已接入快递服务SFExpressClassAdapter 【智能物流平台】发起发货请求 【顺丰系统】创建订单 | 单号SFORD-2001 | 电话13700137000 | 地址北京市朝阳区 ✅ 类适配器平台订单[ORD-2001] 已映射为顺丰单号[SFORD-2001] 【智能物流平台】查询物流信息 [平台统一格式] 快递[SFORD-2001]已揽收运输中4. 对象适配器 vs 类适配器核心对比对比项对象适配器组合类适配器继承实现方式适配器持有 Adaptee 实例组合/委托适配器继承 Adaptee 类UML 关系Adapter o-- Adaptee聚合Adapter –适配多个类✅ 可以适配多个 Adaptee甚至动态切换❌ Java 单继承只能适配一个类适配子类✅ 可以适配 Adaptee 及其所有子类❌ 只能适配特定的父类重写 Adaptee 方法❌ 无法重写 Adaptee 的方法✅ 可以重写父类方法定制行为耦合度较低面向接口/组合较高强依赖父类实现灵活性高运行时可替换 Adaptee 实例低编译时就确定了 AdapteeJava 推荐✅优先使用⚠️ 仅在需要重写被适配者方法时使用5. 类适配器的适用场景虽然对象适配器更灵活但类适配器在以下场景仍有价值场景说明需要重写被适配者的部分方法继承可以直接Override对象适配器需要额外包装被适配者是抽象类继承抽象类并实现目标接口同时补全抽象方法框架内部实现Java 的Arrays.asList()返回的 List 就是类适配器的典型应用6. 适配器模式与“组合优于继承”原则对象适配器类适配器组合优于继承✅ 遵循持有实例❌ 违反使用继承接口隔离✅ 只依赖目标接口⚠️ 继承了 Adaptee 的所有方法开闭原则✅ 新增适配器无需修改现有代码✅ 同样符合简单记忆对象适配器是“传话筒”——拿着别人的东西去翻译类适配器是“混血儿”——既继承父类基因又实现目标接口。7. 六大设计原则体现原则类适配器体现单一职责适配器只负责接口转换开闭原则新增适配器无需修改现有类里氏替换适配器可替换目标接口依赖倒置客户端依赖抽象ShippingService接口隔离目标接口定义精简迪米特法则客户端只与ShippingService交互附 类适配器 UML源码startuml title Java 23 种设计模式从踩坑到精通 footer 折哥 | 智能物流与Java实战 1. 全局样式配置 skinparam backgroundColor #FEFEFE skinparam shadowing false skinparam classBorderColor #333333 skinparam classFontColor #1A1A1A skinparam classFontSize 14 skinparam noteFontSize 12 skinparam noteFontColor #555555 skinparam arrowColor #555555 skinparam classBackgroundColor #F9F9F9 2. 目标接口统一的快递服务 interface ShippingService { void createOrder(String orderNo, String receiver, String address) void cancelOrder(String orderNo) String trackOrder(String orderNo) double calculateFee(double weight, String destination) } note right of ShippingService b目标接口Target/b -- 物流平台期望的统一快递接口 end note 3. 被适配者顺丰快递 class SFExpress { void sfCreate(String sfOrderNo, String phone, String addr) void sfCancel(String sfOrderNo) String sfQuery(String sfOrderNo) double sfGetPrice(double kg, String city) } note right of SFExpress b被适配者Adaptee/b -- 顺丰原有接口方法名和参数 与平台标准完全不同 end note 4. 适配器顺丰类适配器继承 实现 class SFExpressClassAdapter { void createOrder(String orderNo, String receiver, String address) void cancelOrder(String orderNo) String trackOrder(String orderNo) double calculateFee(double weight, String destination) } note right of SFExpressClassAdapter b类适配器Class Adapter/b -- 同时继承 SFExpress 并实现 ShippingService 通过继承获得父类能力重写方法做适配 end note 5. 客户端物流平台 class LogisticsPlatform { - shippingService : ShippingService void setShippingService(ShippingService service) void ship(String orderNo, String receiver, String address) void track(String orderNo) } note right of LogisticsPlatform b客户端Client/b -- 只依赖统一的 ShippingService 完全不关心底层适配方式 end note 6. 关系连线 ShippingService |.. SFExpressClassAdapter : 实现 SFExpressClassAdapter -| SFExpress : 继承 LogisticsPlatform o-- ShippingService : 使用 enduml 《Java 23 种设计模式从踩坑到精通》快速导航开篇系列介绍与目录正篇适配器模式 Adapter —— 让不兼容的接口也能一起工作当前番外 · 适配器模式 × 类适配器 vs 对象适配器你在这里创建型模式汇总结构型模式汇总行为型模式汇总 关注《Java 23 种设计模式从踩坑到精通》用 25 篇文章彻底吃透设计模式。福利预告全系列代码及 UML 源码将在完结时统一打包开放点击「关注」「收藏」第一时间获取。 除了设计模式我也在深挖智能物流实战WMS、托盘调度、机器学习落地。欢迎点击头像看看专栏 《出版社物流WMS智能调度实战》、《电商多平台电子面单对接实战》。技术相通思路可鉴。