当前位置: 首页 > news >正文

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);}// ========== 第三方 SDK(Adaptee,不可修改) ==========publicclassLogisticsTracker{publicStringqueryTrajectory(StringwaybillNo,StringcarrierCode){// 实际会调用第三方 APIreturn"{\"status\":\"运输中\",\"location\":\"北京分拨中心\"}";}}// ========== 对象适配器(推荐) ==========publicclassLogisticsTrackerAdapterimplementsTrajectoryService{privateLogisticsTrackertracker;// 持有 AdapteepublicLogisticsTrackerAdapter(LogisticsTrackertracker){this.tracker=tracker;}@OverridepublicTrajectoryResultquery(TrajectoryQueryquery){// 1. 参数转换:把 WMS 统一参数转为第三方需要的格式StringwaybillNo=query.getWaybillNo();StringcarrierCode=query.getCarrier();// 2. 调用第三方方法StringjsonResponse=tracker.queryTrajectory(waybillNo,carrierCode);// 3. 结果转换:把 JSON 转为 WMS 统一的结果对象returnparseJsonToResult(jsonResponse);}privateTrajectoryResultparseJsonToResult(Stringjson){TrajectoryResultresult=newTrajectoryResult();if(json.contains("运输中"))result.setStatus("运输中");if(json.contains("北京分拨中心"))result.setLocation("北京分拨中心");returnresult;}}// ========== 客户端调用 ==========LogisticsTrackerthirdPartyTracker=newLogisticsTracker();TrajectoryServiceadapter=newLogisticsTrackerAdapter(thirdPartyTracker);TrajectoryResultresult=adapter.query(query);// 业务代码只依赖 TrajectoryService

💬白话:适配器就像一个“转接头”,把第三方 SDK 的接口转成我们系统统一的接口。业务代码完全不知道底层调用的是哪个 SDK。

1.2 场景二:咖啡加料(装饰器模式)

基础饮品是SimpleCoffee,顾客可以自由加牛奶、加糖、加奶油。每种配料都会影响最终价格和描述。如果为每种组合建一个子类(CoffeeWithMilkCoffeeWithMilkAndSugar……),类数量会爆炸。

装饰器模式的解决思路:用一系列包装器对象去“包裹”核心对象,每个包装器在核心行为前后添加自己的功能。接口从未改变,始终是Beverage

// ========== 抽象构件 ==========publicinterfaceBeverage{StringgetDescription();doublecost();}// ========== 具体构件:基础咖啡 ==========publicclassSimpleCoffeeimplementsBeverage{@OverridepublicStringgetDescription(){return"黑咖啡";}@Overridepublicdoublecost(){return10.0;}}// ========== 抽象装饰器 ==========publicabstractclassCondimentDecoratorimplementsBeverage{protectedBeveragebeverage;// 被装饰的对象publicCondimentDecorator(Beveragebeverage){this.beverage=beverage;}}// ========== 具体装饰器 ==========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;}}// ========== 客户端调用 ==========Beveragecoffee=newSimpleCoffee();coffee=newMilk(coffee);// 加牛奶coffee=newSugar(coffee);// 加糖System.out.println(coffee.getDescription()+" 价格:"+coffee.cost());// 黑咖啡 + 牛奶 + 糖 价格:14.0

💬白话:装饰器像俄罗斯套娃,一层套一层,每一层都增加一点新功能。最厉害的是——所有层都实现同一个Beverage接口,客户端完全不用区分“这是基础咖啡还是加了牛奶的咖啡”。

1.3 关键差异已浮现

  • 适配器改变了接口:客户端用的TrajectoryService和第三方 SDK 的LogisticsTracker接口完全不同;
  • 装饰器从未改变接口SimpleCoffeeMilkSugar都实现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,将其适配为 ReaderReaderreader=newInputStreamReader(newFileInputStream("data.txt"),StandardCharsets.UTF_8);

为什么是适配器?

  • InputStreamReader两个不同的抽象类,接口完全不同;
  • InputStreamReader继承了Reader,内部持有一个InputStream实例;
  • 它在read()方法中调用InputStream.read(),并完成字节→字符的转换
  • 这就是适配器的核心——改变接口 + 转换格式

4.2BufferedInputStream(装饰器模式)

// BufferedInputStream 是典型的装饰器// Component: InputStream// ConcreteComponent: FileInputStream// Decorator: FilterInputStream(抽象装饰器)// ConcreteDecorator: BufferedInputStreamInputStreamin=newBufferedInputStream(newFileInputStream("data.bin"));

为什么是装饰器?

  • FileInputStreamBufferedInputStream都继承自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智能调度实战》。技术相通,思路可鉴。

http://www.jsqmd.com/news/1196483/

相关文章:

  • 伯爵中国官方售后服务中心|服务热线及详细地址权威信息公示(2026年7月最新) - 亨得利钟表维修中心
  • Patran中可直接运行的圆柱体参数化建模全套PCL脚本(含菜单、材料、网格、载荷与结果提取)
  • 2026深圳靠谱搬家公司推荐|10家本地大型正规直营搬家公司排名,本土老牌无外包、无隐形消费、合规避坑全指南 - 厚道搬家
  • AI 应用的技术债务管理:从 Prompt 碎片到架构腐化的系统性治理
  • 深圳本地企业GEO流量增长工具怎么选?高性价比工具推荐 - 小随科技
  • 2026 年新发布:宝丰热门的热浸塑排水管道供应厂家深度剖析,别再用传统管道了!热浸塑技术如何彻底改变排水难题 - 行业推荐【认证官】
  • 故障自愈与自动恢复机制:从手动重启到无人值守
  • 大模型配置优化实战:从参数调优到生产部署完整指南
  • Jetson TK1系统检查全指南:L4T版本、Ubuntu兼容性与硬件诊断
  • 稳健回归实战指南:应对异常值的5种Python方法与选型策略
  • 使用 Docker 安装
  • 六安本地企业GEO营销工具全维度横向测评推荐:2026年AI搜索优化选型7大维度解析 - 企业新闻快传
  • 2026 年施秉有实力的回收抗氧剂供货商深度解析,揭秘:别让这些“隐形杀手”毁了你的材料 - 行业鉴选官
  • LangChain Agent 执行图可视化:用 Mermaid 自动生成 Agent 的运行时流程图
  • 慈溪恋爱转账纠纷律师推荐|分手后起诉前女友追回花费,原配起诉小三返还财产,王睿涵律师专注赠与合同纠纷 - 边虞技术
  • Sqribble文档工业化流水线:模板驱动的结构化排版系统
  • Next.js 14 App Router 架构深入:Server Actions 的安全边界与缓存设计
  • 2026年AI应用落地怎么选:按业务流程需求拆解的选型指南 - 科技焦点
  • Python五大工业级数据集实战解析:从Iris到Diamonds的工程化应用
  • Sqribble:模板驱动的文档操作系统与自动化工作流
  • 家住北京、天津,闲置老酒想上门变现?六家多年实体收酒机构推荐 - 深鉴新闻
  • N皇后遗传算法实战:Python手写GA核心模块与调参避坑指南
  • 【学习笔记】Prompt 工程方法论:从「玄学」到工程化(30/35)
  • 伯爵中国官方售后服务中心|服务热线及门店详细地址权威信息通告(2026年7月最新) - 亨得利钟表维修中心
  • RAG 多模态检索方案:图片 + 文本的联合 Embedding 工程实践
  • 江诗丹顿香港官方售后攻略|2026年7月最新网点地址及客户服务电话权威发布 - 江诗丹顿官方服务中心
  • ChunkDot:百万级稀疏向量的高效余弦相似度计算方案
  • 阜阳本地企业GEO营销工具全维度横向测评推荐:2026年选型7大维度深度解析 - 企业新闻快传
  • 卡美德生物科普Phosphatidylserine(磷脂酰丝氨酸)
  • 恭喜!AMD AI 技术征文大赛获奖名单公布