Java 23 种设计模式:从踩坑到精通 | 番外:适配器 vs 装饰器 —— 一个改接口,一个不改接口 Java 23 种设计模式从踩坑到精通 | 番外适配器 vs 装饰器 —— 一个改接口一个不改接口摘要适配器模式和装饰器模式都通过“包装”对象来工作但适配器的核心是改变接口让不兼容的类协同工作装饰器的核心是不改变接口动态增强功能。本文从物流轨迹对接和咖啡加料两个完整场景出发结合详细代码实现、UML 对比分析和 JDK 源码InputStreamReadervsBufferedInputStream帮你彻底分清这对“包装型”结构型模式。️本文阅读地图3 分钟速览为什么对接第三方 SDK 要用适配器为什么咖啡加料不用继承而用装饰器UML 对比一个改接口一个不改接口手写物流轨迹适配器 vs 咖啡加料装饰器完整代码对比JDKInputStreamReader适配器vsBufferedInputStream装饰器源码级分析面试必问“适配器和装饰器怎么区分InputStreamReader用了哪个”《Java 23 种设计模式从踩坑到精通》开篇系列介绍与目录 当前番外 · 适配器 vs 装饰器 返回系列总目录文章目录Java 23 种设计模式从踩坑到精通 | 番外适配器 vs 装饰器 —— 一个改接口一个不改接口1. 从“物流轨迹对接”和“咖啡加料”两个场景说起1.1 场景一物流轨迹对接适配器模式1.2 场景二咖啡加料装饰器模式1.3 关键差异已浮现2. UML 对比3. 三大核心区别4. JDK 源码深度对比4.1 InputStreamReader适配器模式4.2 BufferedInputStream装饰器模式5. 什么时候不该用6. 面试必问 追问连环炮 《Java 23 种设计模式从踩坑到精通》快速导航1. 从“物流轨迹对接”和“咖啡加料”两个场景说起1.1 场景一物流轨迹对接适配器模式第三方物流 SDK 提供了LogisticsTracker类方法签名是queryTrajectory(String waybillNo, String carrierCode)返回 JSON 字符串。但你整个 WMS 系统统一使用的是TrajectoryService接口——参数是TrajectoryQuery对象返回的是TrajectoryResult对象。接口格式完全对不上。更麻烦的是第三方 SDK 的源码不能修改而你的业务代码已经深度依赖了TrajectoryService接口。适配器模式的解决思路创建一个“转接头”实现系统统一的TrajectoryService接口内部持有第三方LogisticsTracker完成参数转换、调用转发和结果转换。// 系统已有的目标接口Target publicinterfaceTrajectoryService{TrajectoryResultquery(TrajectoryQueryquery);}// 第三方 SDKAdaptee不可修改 publicclassLogisticsTracker{publicStringqueryTrajectory(StringwaybillNo,StringcarrierCode){// 实际会调用第三方 APIreturn{\status\:\运输中\,\location\:\北京分拨中心\};}}// 对象适配器推荐 publicclassLogisticsTrackerAdapterimplementsTrajectoryService{privateLogisticsTrackertracker;// 持有 AdapteepublicLogisticsTrackerAdapter(LogisticsTrackertracker){this.trackertracker;}OverridepublicTrajectoryResultquery(TrajectoryQueryquery){// 1. 参数转换把 WMS 统一参数转为第三方需要的格式StringwaybillNoquery.getWaybillNo();StringcarrierCodequery.getCarrier();// 2. 调用第三方方法StringjsonResponsetracker.queryTrajectory(waybillNo,carrierCode);// 3. 结果转换把 JSON 转为 WMS 统一的结果对象returnparseJsonToResult(jsonResponse);}privateTrajectoryResultparseJsonToResult(Stringjson){TrajectoryResultresultnewTrajectoryResult();if(json.contains(运输中))result.setStatus(运输中);if(json.contains(北京分拨中心))result.setLocation(北京分拨中心);returnresult;}}// 客户端调用 LogisticsTrackerthirdPartyTrackernewLogisticsTracker();TrajectoryServiceadapternewLogisticsTrackerAdapter(thirdPartyTracker);TrajectoryResultresultadapter.query(query);// 业务代码只依赖 TrajectoryService白话适配器就像一个“转接头”把第三方 SDK 的接口转成我们系统统一的接口。业务代码完全不知道底层调用的是哪个 SDK。1.2 场景二咖啡加料装饰器模式基础饮品是SimpleCoffee顾客可以自由加牛奶、加糖、加奶油。每种配料都会影响最终价格和描述。如果为每种组合建一个子类CoffeeWithMilk、CoffeeWithMilkAndSugar……类数量会爆炸。装饰器模式的解决思路用一系列包装器对象去“包裹”核心对象每个包装器在核心行为前后添加自己的功能。接口从未改变始终是Beverage。// 抽象构件 publicinterfaceBeverage{StringgetDescription();doublecost();}// 具体构件基础咖啡 publicclassSimpleCoffeeimplementsBeverage{OverridepublicStringgetDescription(){return黑咖啡;}Overridepublicdoublecost(){return10.0;}}// 抽象装饰器 publicabstractclassCondimentDecoratorimplementsBeverage{protectedBeveragebeverage;// 被装饰的对象publicCondimentDecorator(Beveragebeverage){this.beveragebeverage;}}// 具体装饰器 publicclassMilkextendsCondimentDecorator{publicMilk(Beveragebeverage){super(beverage);}OverridepublicStringgetDescription(){returnbeverage.getDescription() 牛奶;}Overridepublicdoublecost(){returnbeverage.cost()3.0;}}publicclassSugarextendsCondimentDecorator{publicSugar(Beveragebeverage){super(beverage);}OverridepublicStringgetDescription(){returnbeverage.getDescription() 糖;}Overridepublicdoublecost(){returnbeverage.cost()1.0;}}// 客户端调用 BeveragecoffeenewSimpleCoffee();coffeenewMilk(coffee);// 加牛奶coffeenewSugar(coffee);// 加糖System.out.println(coffee.getDescription() 价格coffee.cost());// 黑咖啡 牛奶 糖 价格14.0白话装饰器像俄罗斯套娃一层套一层每一层都增加一点新功能。最厉害的是——所有层都实现同一个Beverage接口客户端完全不用区分“这是基础咖啡还是加了牛奶的咖啡”。1.3 关键差异已浮现适配器改变了接口客户端用的TrajectoryService和第三方 SDK 的LogisticsTracker接口完全不同装饰器从未改变接口SimpleCoffee、Milk、Sugar都实现Beverage客户端始终面向Beverage编程。2. UML 对比3. 三大核心区别对比维度适配器模式装饰器模式是否改变接口✅ 改变Target ≠ Adaptee❌ 不改变始终是 Component意图解决接口不兼容让不兼容的类协同工作动态增强功能不修改原有代码嵌套能力通常一对一可层层嵌套任意组合客户端感知客户端面向 Target 接口不感知 Adaptee客户端始终面向 Component 接口典型应用InputStreamReader字节流→字符流BufferedInputStream给字节流加缓冲4. JDK 源码深度对比4.1InputStreamReader适配器模式// InputStreamReader 是典型的对象适配器// Target: Reader字符流// Adaptee: InputStream字节流// Adapter: InputStreamReader 持有 InputStream将其适配为 ReaderReaderreadernewInputStreamReader(newFileInputStream(data.txt),StandardCharsets.UTF_8);为什么是适配器InputStream和Reader是两个不同的抽象类接口完全不同InputStreamReader继承了Reader内部持有一个InputStream实例它在read()方法中调用InputStream.read()并完成字节→字符的转换这就是适配器的核心——改变接口 转换格式。4.2BufferedInputStream装饰器模式// BufferedInputStream 是典型的装饰器// Component: InputStream// ConcreteComponent: FileInputStream// Decorator: FilterInputStream抽象装饰器// ConcreteDecorator: BufferedInputStreamInputStreaminnewBufferedInputStream(newFileInputStream(data.bin));为什么是装饰器FileInputStream和BufferedInputStream都继承自InputStream接口从未改变BufferedInputStream只是在原有read()方法前后添加了缓冲逻辑你可以继续嵌套new DataInputStream(new BufferedInputStream(new FileInputStream()))这就是装饰器的核心——不改变接口 动态增强 可嵌套。5. 什么时候不该用滥用场景应该用什么原因两个模块都由你控制接口可以统一直接统一接口适配器是补救手段不是设计目标只想给对象加日志接口本身没变装饰器适配器会改变接口过度设计只想控制访问权限接口本身没变代理模式代理控访问装饰加功能适配改接口功能增强逻辑简单且固定直接修改类或继承装饰器引入多层嵌套调试成本高6. 面试必问 追问连环炮基础必问适配器和装饰器的核心区别→ 适配器改变接口装饰器不改变接口。InputStreamReader用了哪种模式→ 适配器模式对象适配器将InputStream适配为Reader。BufferedInputStream用了哪种模式→ 装饰器模式给InputStream添加缓冲功能接口没变。面试官追问“Collections.synchronizedList()是装饰器还是适配器” 装饰器。它包装List添加线程安全功能接口始终是List没有改变。“Collections.unmodifiableList()是装饰器还是代理” 更接近代理。它的主要目的是控制访问禁止修改而非增强功能。“对象适配器和类适配器有什么区别” 对象适配器用组合持有 Adaptee 实例更灵活可以适配多个不同的 Adaptee类适配器用继承继承 Adaptee受限于 Java 单继承。日常开发首选对象适配器。恭喜如果你能立刻说出InputStreamReader是适配器接口变了、BufferedInputStream是装饰器接口没变你已经掌握了这对“包装型”结构型模式最核心的区分点。延伸阅读本文是适配器模式与装饰器模式的深度对比。如果你想先了解每种模式的完整原理、UML、代码实现和面试题可以先阅读正篇——适配器模式让不兼容的接口也能一起工作 和 装饰器模式比继承更灵活的扩展方式再回来看这篇对比效果更佳。 《Java 23 种设计模式从踩坑到精通》快速导航开篇系列介绍与目录当前番外 · 适配器 vs 装饰器 —— 一个改接口一个不改接口你在这里 延伸阅读适配器模式正篇 装饰器模式正篇创建型模式汇总结构型模式汇总行为型模式汇总 关注《Java 23 种设计模式从踩坑到精通》用 25 篇文章彻底吃透设计模式。福利预告全系列代码及 UML 源码将在完结时统一打包开放点击「关注」「收藏」第一时间获取。 除了设计模式我也在深挖智能物流实战WMS、托盘调度、机器学习落地。欢迎点击头像看看专栏 《出版社物流WMS智能调度实战》。技术相通思路可鉴。

相关新闻

最新新闻

工业现场疑难软故障实录:01 为什么系统以前能跑,现在突然不行了?

工业现场疑难软故障实录:01 为什么系统以前能跑,现在突然不行了?

第一篇:为什么系统以前能跑,现在突然不行了? ——系统崩溃的那一天,往往不是问题开始的那一天 那条生产线已经平稳运行了整整八年。 维修部接到电话时,所有人都觉得不可能。因为这是一条全厂公认的最稳定产线。八年来,几乎没有重大停机,没有频繁报警,维修记录也干干…

2026/7/16 0:15:11
【初阶·安全】大模型常见攻击向量认知深度解析:Prompt Injection、越狱攻击与数据投毒的攻防全景

【初阶·安全】大模型常见攻击向量认知深度解析:Prompt Injection、越狱攻击与数据投毒的攻防全景

【初阶安全】大模型常见攻击向量认知深度解析:Prompt Injection、越狱攻击与数据投毒的攻防全景 专栏:《AI 工程与安全深度实战》 第8轮第2篇 核心痛点:大模型应用正从"对话玩具"演进为接入 RAG、工具调用与 Agent 自主行动的生产系统,但绝大多数团队的安全认知仍…

2026/7/16 0:15:11
长上下文序列并行训练深度解析:从 Ring Attention 到 Ulysses 的百万 token 分布式注意力架构

长上下文序列并行训练深度解析:从 Ring Attention 到 Ulysses 的百万 token 分布式注意力架构

长上下文序列并行训练深度解析:从 Ring Attention 到 Ulysses 的百万 token 分布式注意力架构 核心痛点:当上下文从 32K 扩展到百万 token,单卡显存被注意力激活与 KV 占满、单步计算量爆炸,而数据并行无法切分序列维度、朴素张量并行又受限于头数与互联带宽——长上下文训…

2026/7/16 0:15:11
苹果的“拥抱”与“超越”:解构基于 Google Gemini 的新一代 AI 架构

苹果的“拥抱”与“超越”:解构基于 Google Gemini 的新一代 AI 架构

苹果的“拥抱”与“超越”:解构基于 Google Gemini 的新一代 AI 架构 在移动 AI 的演进史册中,2026 年的这一个章节注定将被浓墨重彩地书写。长久以来,我们习惯了苹果在封闭生态中的孤傲姿态,从 A 系列芯片的自主研发到 Neural En…

2026/7/16 0:15:11
如果你想获得他人的关注,请先证明你付出了努力

如果你想获得他人的关注,请先证明你付出了努力

如果你想获得他人的关注,请先证明你付出了努力 在技术圈子里,我们经常能看到这样一种现象:有人在技术论坛或开源社区提出了一个问题,标题草草了事,正文只有寥寥数语,甚至直接甩出一张代码截图或一段报错信…

2026/7/16 0:15:11
Windows系统文件d3dx9_36.dll丢失找不到问题解决

Windows系统文件d3dx9_36.dll丢失找不到问题解决

在使用电脑系统时经常会出现丢失找不到某些文件的情况,由于很多常用软件都是采用 Microsoft Visual Studio 编写的,所以这类软件的运行需要依赖微软Visual C运行库,比如像 QQ、迅雷、Adobe 软件等等,如果没有安装VC运行库或者安装…

2026/7/16 0:10:11

月新闻