源码级详解:以 java-design-patterns 中 Captain 与 FishingBoat 为例)
示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载本文以 java-design-patterns 仓库中 adapter 模块localization/it/adapter/README.md对应的模式讲解为骨架结合模块源码、测试与构建配置深入剖析适配器模式Adapter Pattern又称 Wrapper 包装器的意图、角色划分、对象适配器的实现方式、适用场景与类/对象两种变体的取舍。读完本文你将能够理解适配器如何让接口不兼容的类协同工作并能直接在 Java 项目中复刻这一结构。模式概览一个解决接口不兼容问题的结构型模式适配器模式属于结构型模式Structural Pattern出自经典的《Design Patterns: Elements of Reusable Object-Oriented Software》GoF 四卷书一书在仓库元信息中标记了Gang of Four标签。意图Intent将某个类的接口转换成客户端期望的另一个接口。适配器模式让那些因接口不兼容而原本无法协作的类能够一起工作。用一句话概括适配器模式允许你把一个原本不兼容的对象包装进一个适配器中使其与另一个类兼容。用维基百科的定义来说在软件工程中适配器模式是一种软件设计模式它允许已有类的接口被当作另一种接口来使用。它常被用来让已有类在不修改自身源代码的前提下与其他类协同工作。这一模式之所以重要是因为现实中的类往往各自定义接口而当我们要复用它们时接口签名却不一定对得上——适配器正是那个翻译层。现实世界的类比读卡器、电源适配器与翻译官原文档给出了三个贴近生活的类比帮助理解适配器的本质读卡器存储卡上的照片要转移到电脑上需要一种与电脑端口兼容的适配设备读卡器就是一个 adapter——它把存储卡的接口转换成电脑能识别的 USB 接口。电源适配器三脚插头无法插入两孔插座需要一个电源适配器让它变得兼容。翻译官同声传译把一个人说的话翻译给另一个人让两个语言不通的人能够交流。这三个例子共同揭示了适配器的核心价值在不改变两端被适配对象与客户端的前提下通过一个中间层完成接口的转换。模式角色与 UML 类图适配器模式通常由四个角色构成在仓库源码中一一对应角色模式职责本示例中的实现Target目标接口客户端所期望的接口RowingBoatClient客户端只面向 Target 接口编程CaptainAdaptee被适配者已存在但接口不兼容的类FishingBoatAdapter适配器将 Adaptee 的接口转换为 TargetFishingBoatAdapter适配器模式 UML 类图类图直观地展示了依赖方向Captain只依赖RowingBoat接口FishingBoatAdapter实现RowingBoat并持有组合FishingBoat从而把sail()包装成row()。源码级示例海盗来袭船长必须逃离仓库中的示例讲述了一个完整的故事船长只会驾驶划艇rowing boat完全不会开船一天海盗来了船长必须逃走可手边只有一艘渔船fishing boat。我们需要一个适配器让船长用他会划桨的技能驾驶渔船逃生。整个示例位于 adapter/src/main/java/com/iluwatar/adapter 目录下共四个核心类。1. TargetRowingBoat接口RowingBoat是客户端期望的接口也是整个模式的目标public interface RowingBoat { void row(); }2. AdapteeFishingBoat类FishingBoat是需要复用的既有类但它暴露的方法是sail()航行与客户端需要的row()划桨接口不匹配Slf4j final class FishingBoat { void sail() { LOGGER.info(The fishing boat is sailing); } }从源码可以看到FishingBoat被声明为final包级私有类sail()也是包级方法并通过 Lombok 的Slf4j注解输出日志——这正是我们不打算修改既有类的现实写照。3. ClientCaptain类Captain是模式的客户端它只面向RowingBoat接口编程Setter NoArgsConstructor AllArgsConstructor public final class Captain { private RowingBoat rowingBoat; void row() { rowingBoat.row(); } }注意Captain持有的是接口类型RowingBoat而非具体类因此只要给它注入任何实现了RowingBoat的对象它就能划船。Lombok 注解Setter、NoArgsConstructor、AllArgsConstructor分别为它生成了 setter、无参构造和全参构造方便在运行时替换所依赖的船只。4. AdapterFishingBoatAdapter类FishingBoatAdapter是模式的核心——适配器。它实现RowingBoat接口内部通过组合持有FishingBoat把row()调用转发给渔船的sail()public class FishingBoatAdapter implements RowingBoat { private final FishingBoat boat new FishingBoat(); public final void row() { boat.sail(); } }适配器的 Javadoc 明确写道Adapter class. Adapts the interface of the device (FishingBoat) into RowingBoat interface expected by the client (Captain).——即把被适配设备的接口转换为客户端期望的接口。组装与运行在 App.java 的main方法中完成组装并演示public static void main(final String[] args) { // The captain can only operate rowing boats but with adapter he is able to // use fishing boats as well var captain new Captain(new FishingBoatAdapter()); captain.row(); }运行后由于FishingBoat.sail()使用 SLF4J 记录日志控制台输出如下时间戳会随运行时刻变化10:25:08.074 [main] INFO com.iluwatar.adapter.FishingBoat -- The fishing boat is sailing至此船长借助适配器成功驾驶渔船逃离海盗。整个过程没有修改FishingBoat的源代码也没有修改Captain的代码仅通过新增一个FishingBoatAdapter就完成了接口桥接。调用链与时序验证从 row() 到 sail()为了确认上述调用链的真实性仓库提供了单元测试 AdapterPatternTest.java它用 Mockito 的spy包装FishingBoatAdapter再注入Captain最后用verify断言调用链Test void testAdapter() { var captain (Captain) beans.get(ROWING_BEAN); // when captain moves captain.row(); // the captain internally calls the battleship object to move var adapter (RowingBoat) beans.get(FISHING_BEAN); verify(adapter).row(); }测试的 Javadoc 也说明了意图when we use the row() method on a captain bean(client), it is internally calling sail method on the fishing boat object.测试文件把Captain与适配器作为两个 bean 存入 Map模拟了依赖注入的场景验证了客户端调用row()→ 适配器转发 →FishingBoat.sail()的完整链路。适配器模式时序图时序图展示了消息的流转顺序Captain调用FishingBoatAdapter.row()适配器内部再调用FishingBoat.sail()。从构建配置看adapter/pom.xml 依赖了slf4j-api、logback-classic日志输出、junit-jupiter-engine与mockito-core测试并通过maven-assembly-plugin将com.iluwatar.adapter.App指定为主类说明该模块可以独立打包运行仓库根目录提供了mvnw/mvnw.cmd包装脚本可在本地通过 Maven Wrapper 执行构建与测试。适用场景什么时候使用适配器模式原文档列出了四种典型适用情形在实际项目中可按此对照想使用一个已有的类但它的接口与你的需求不匹配——这是最直接的场景适配器负责把接口翻译成你需要的样子。想创建一个可复用的类它能与无关的、事先未预料的类协作即那些接口不一定兼容的类。适配器让客户端与具体实现彻底解耦。需要同时使用多个已有的子类但逐一为它们创建子类来适配接口不切实际——此时对象适配器可以适配其父类的接口一次适配即可覆盖所有子类。在使用第三方库的大多数应用中适配器常被用作应用与第三方库之间的中间层把应用与库解耦。当需要更换库时只需为新库写一个新的适配器而无需改动应用代码。最后一个场景正是适配器模式在企业级开发中的最大价值它是隔离外部依赖变更的廉价方案。类适配器 vs 对象适配器两种变体的取舍适配器模式存在两种变体它们在灵活性上有着明显差异。本仓库示例使用的是对象适配器通过组合持有FishingBoat源码 App.java 的 Javadoc 中也明确说明There are two variations of the Adapter pattern: The class adapter implements the adaptees interface whereas the object adapter uses composition to contain the adaptee in the adapter object. This example uses the object adapter approach.**类适配器Class Adapter**的特点通过继承被适配类来完成适配因此绑定于一个特定的被适配类无法适配一个类及其全部子类因为适配器是被适配类的子类所以可以覆盖被适配类的一部分行为只引入一个对象不需要额外的指针间接引用被适配类。**对象适配器Object Adapter**的特点单个适配器可以同时与多个被适配类协作包括被适配类及其所有子类还能一次性为所有被适配类增加功能但覆盖被适配类行为变得更困难——需要先创建被适配类的子类再让适配器引用这个子类而非被适配类本身。选择建议当需要覆盖被适配者的行为且被适配类不会扩展时类适配器更直接当需要适配一个类层级、追求组合的灵活性与低耦合时对象适配器本仓库的默认选择更符合优先组合而非继承的现代工程实践。JDK 中的真实案例适配器模式在 JDK 中随处可见原文档列出了如下经典实例可作为学习参照java.util.Arrays#asList()把数组适配为List接口java.util.Collections#list()把Enumeration适配为Listjava.util.Collections#enumeration()把Collection适配为Enumerationjavax.xml.bind.annotation.adapters.XmlAdapterJAXB 中用于在 XML 表示与 Java 类型之间做适配的机制。此外java.io.InputStreamReader与java.io.OutputStreamWriter也是经典的流适配器把字节流适配为字符流GUI 组件库中也常通过插件式适配器在不同组件接口之间转换。总结适配器模式通过引入一个中间适配层让接口不兼容但功能匹配的类得以协作。在 java-design-patterns 的 adapter 模块中RowingBoatTarget、CaptainClient、FishingBoatAdaptee、FishingBoatAdapterAdapter四个角色分工清晰采用对象适配器组合实现不修改任何既有类的源码测试与构建配置完整可独立运行验证调用链。掌握适配器模式后你在面对新系统要复用老代码更换第三方库整合异构组件等场景时就有了一个低侵入、高内聚的解决方案——这正是它在 GoF 模式中长期保持高频实用性的原因。赞分享示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载相关推荐Druid SQL 解析器重构实录统一 DialectFeature 门控模式与 tableAlias() 方法分解Druid SQL 解析器重构实录统一 DialectFeature 门控模式与 tableAlias 方法分解 导读 本文以 Druid阿里云 DataW示例工程教程Java 适配器模式Adapter Pattern实战解析以 java-design-patterns 项目为例实现不兼容接口的无缝集成Java 适配器模式Adapter Pattern实战解析以 java design patterns 项目为例实现不兼容接口的无缝集成 适配器模式Ad示例工程教程Java 适配器模式实战解析以 java-design-patterns 仓库的 FishingBoatAdapter 为例Java 适配器模式实战解析以 java design patterns 仓库的 FishingBoatAdapter 为例 导读 本文以当前仓库 java示例工程教程上一篇AReaL 自定义 Agent Workflow 开发指南为 RL 训练接入任意 Agent 框架下一篇集成学习算法全解析从AdaBoost、GBDT到XGBoost与LightGBMPython-100-Days实战篇创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考