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

设计模式--适配器模式

前言

想象这个真实场景:

  1. 你有一个新买的、超酷的蓝牙音箱(目标接口:蓝牙连接)。 你只想用手机蓝牙轻松连上它放音乐。

  2. 你还有一个珍藏多年的老式磁带播放机(需要适配的对象:Adaptee)。 它音质很棒,但它只有3.5mm耳机孔(旧接口),没有蓝牙功能。

  3. 问题来了: 你的手机(只有蓝牙)想播放音乐,但无法直接连接老式磁带机(只有3.5mm孔)。你想用磁带机放手机里的歌,怎么办?

解决方案(没有适配器):

  • 方案A(改磁带机): 你是个电子高手,拆开磁带机,焊上一个蓝牙模块。缺点: 风险大(可能弄坏)、成本高、复杂、破坏了原始设备。

  • 方案B(换手机): 买个带3.5mm耳机孔的老手机。缺点: 为了用旧设备换新手机?荒谬!成本更高,牺牲了新手机的功能。

  • 方案C(不用磁带机): 放弃你心爱的磁带机。缺点: 浪费资源,你失去了一个好音源。

解决方案(使用适配器):

  • 你买了一个“蓝牙音频接收器”(这就是适配器!)。 这个小设备:

    • 有一头是 蓝牙(实现目标接口),可以被你的新手机搜索并连接。

    • 另一头是 3.5mm耳机孔(连接Adaptee),可以插到你老磁带机的“AUX IN”或“LINE IN”接口上。

核心思想

适配器模式的核心目的是 解决接口不兼容的问题。它允许两个原本因为接口不同而无法协同工作的类能够一起工作。

        为了便于理解我们把上边的例子换成插头:你有一个欧洲标准的插头(三脚圆形【EuropeanPlug】),但你的插座是美标的(两脚扁平【AmericanSocket】)。为了让插头能插进插座正常工作,你需要一个转换插头(适配器【Adapter】)。适配器模式解决的问题与此类似:它充当两个不兼容接口之间的桥梁,将一个类的接口转换成客户期望的另一个接口。它让原本由于接口不兼容而无法一起工作的类可以协同工作

在软件设计中

  • 目标接口 (Target Interface): 你希望客户端使用的接口。它定义了客户端期望的方法。(对应 AmericanSocket)

  • 需要适配的类 (Adaptee): 已经存在的类,它拥有客户端需要的功能,但它的接口与目标接口不兼容。(对应 EuropeanPlug)

  • 适配器 (Adapter): 一个类,它实现了目标接口,并持有一个需要适配的类(Adaptee)的实例。适配器将目标接口的调用转换(适配) 成对 Adaptee 已有方法的调用。(对应电源转换器本身)

两种主要实现方式

适配器模式在 Java 中主要有两种实现方式:类适配器(通过继承)和对象适配器(通过组合)。对象适配器更常用且更灵活,是推荐的实践。

1. 对象适配器 (推荐 - 使用组合)

// 1. 目标接口 (Target Interface) - 客户端期望的接口
public interface AmericanSocket {void plugIntoAmericanOutlet();
}// 2. 添加原生美式插头实现
public class AmericanPlug implements AmericanSocket {@Overridepublic void plugIntoAmericanOutlet() {System.out.println("美式插头直接插入美式插座...");}
}// 3. 需要适配的类 (Adaptee) - 已有的功能,但接口不兼容
public class EuropeanPlug {public void plugIntoEuropeanOutlet() {System.out.println("欧洲插头插入了欧洲插座...");}
}// 4. 适配器 (Adapter) - 实现目标接口,持有Adaptee实例,进行转换
public class EuropeanToAmericanAdapter implements AmericanSocket {private EuropeanPlug europeanPlug; // 组合 - 持有Adaptee实例public EuropeanToAmericanAdapter() {this.europeanPlug = new EuropeanPlug();}@Overridepublic void plugIntoAmericanOutlet() {System.out.println("适配器将美式插座接口转换为欧式接口...");europeanPlug.plugIntoEuropeanOutlet(); // 调用Adaptee的方法实现功能}
}// 5. 客户端代码 (Client) - 只依赖目标接口 客户端现在可以自由选择
public class Client {public static void main(String[] args) {// 使用原生美式插头AmericanSocket american = new AmericanPlug();american.plugIntoAmericanOutlet();// 使用欧式插头(通过适配器)AmericanSocket europeanAdapter = new EuropeanToAmericanAdapter();europeanAdapter.plugIntoAmericanOutlet();// 甚至可以混合使用List<AmericanSocket> devices = Arrays.asList(new AmericanPlug(),              // 原生美式new EuropeanToAmericanAdapter()  // 适配的欧式);devices.forEach(AmericanSocket::plugIntoAmericanOutlet);}
}

输出:

美式插头直接插入美式插座... 适配器将美式插座接口转换为欧式接口... 欧洲插头插入了欧洲插座... 美式插头直接插入美式插座... 适配器将美式插座接口转换为欧式接口... 欧洲插头插入了欧洲插座...

关键点:

  • 适配器 EuropeanToAmericanAdapter 实现了 AmericanSocket 接口。

  • 适配器持有一个 EuropeanPlug 实例(通过组合)。

  • 当客户端调用 plugIntoAmericanOutlet()(目标接口方法)时,适配器内部调用 europeanPlug.plugIntoEuropeanOutlet()(Adaptee的方法)来完成实际工作,并可能进行必要的转换逻辑(这里是打印一条消息)。

2. 类适配器 (使用继承 - 在Java中通常指通过继承Adaptee并实现Target接口)

// 1. 目标接口 (Target Interface) - 客户端期望的接口
public interface AmericanSocket {void plugIntoAmericanOutlet();
}// 2. 添加原生美式插头实现
public class AmericanPlug implements AmericanSocket {@Overridepublic void plugIntoAmericanOutlet() {System.out.println("美式插头直接插入美式插座...");}
}// 3. 需要适配的类 (Adaptee) - 已有的功能,但接口不兼容
public class EuropeanPlug {public void plugIntoEuropeanOutlet() {System.out.println("欧洲插头插入了欧洲插座...");}
}// 4. 适配器 (Adapter) - 实现目标接口,持有Adaptee实例,进行转换
public class EuropeanToAmericanAdapter extends EuropeanPlug implements AmericanSocket {@Overridepublic void plugIntoAmericanOutlet() {System.out.println("适配器将美式插座接口转换为欧式接口...");super.plugIntoEuropeanOutlet(); // 直接调用继承自Adaptee的方法}
}// 5. 客户端代码 (Client) - 只依赖目标接口 客户端现在可以自由选择
public class Client {public static void main(String[] args) {// 使用原生美式插头AmericanSocket american = new AmericanPlug();american.plugIntoAmericanOutlet();// 使用欧式插头(通过适配器)AmericanSocket europeanAdapter = new EuropeanToAmericanAdapter();europeanAdapter.plugIntoAmericanOutlet();// 甚至可以混合使用List<AmericanSocket> devices = Arrays.asList(new AmericanPlug(),              // 原生美式new EuropeanToAmericanAdapter()  // 适配的欧式);devices.forEach(AmericanSocket::plugIntoAmericanOutlet);}
}

关键点:

  • 适配器 EuropeanToAmericanAdapter 继承了 EuropeanPlug (Adaptee) 并 实现了 AmericanSocket (Target) 接口。

  • 适配器直接重用了 EuropeanPlug 的方法(通过继承)。

  • 当客户端调用 plugIntoAmericanOutlet() 时,适配器调用其继承来的 plugIntoEuropeanOutlet() 方法。

为什么对象适配器更常用?

  1. 灵活性: 对象适配器使用组合,一个适配器可以适配任何 EuropeanPlug 的子类(只要接口一致)。类适配器在编译时就固定了它只能适配 EuropeanPlug 或它的特定子类。

  2. 解耦: 对象适配器将适配器与 Adaptee 的实现解耦。适配器只依赖于 Adaptee 的接口。类适配器直接继承了 Adaptee 的实现,耦合度更高。

  3. 遵循“组合优于继承”原则: 组合通常比继承提供更大的灵活性和更少的副作用。

  4. Adaptee 是类或接口均可: 对象适配器也能适配实现了某个接口的 Adaptee。类适配器要求 Adaptee 必须是具体类(Java 不支持多继承,所以 Adaptee 必须是类)。

适配器模式的应用场景

  1. 集成第三方库或遗留代码: 当你需要使用一个功能强大的类库,但其接口与你项目的现有接口不匹配时,创建一个适配器来封装库的调用。

  2. 系统升级/重构: 新版本组件接口改变了,但旧客户端代码仍需调用新组件。可以编写适配器让旧接口调用新组件。

  3. 统一多个类的接口: 系统中存在多个功能类似但接口不同的类,客户端希望用统一的接口调用它们。可以为每个不同的类创建适配器,让它们都实现同一个目标接口。

  4. 创建可复用的类: 设计一个类,期望它能与未来可能出现的、接口未知的类协同工作。可以先定义好目标接口,未来通过适配器来适配新类。

适配器模式的优缺点

  • 优点:

    • 提高类的复用性: 让原本不兼容的类可以一起工作,复用已有的功能。

    • 提高灵活性: 通过更换不同的适配器,可以灵活地使用不同的 Adaptee。

    • 目标与实现解耦: 客户端代码只依赖目标接口,与 Adaptee 的具体实现解耦。

    • 符合开闭原则: 引入新的 Adaptee 类型时,只需添加新的适配器类,无需修改现有客户端代码和目标接口。

  • 缺点:

    • 增加复杂性: 引入了额外的适配器类,增加了系统的类和对象的数量。

    • 过度使用可能导致混乱: 如果系统中适配器过多,可能会使代码变得难以理解和维护。

    • 可能降低效率 (微乎其微): 多了一层间接调用,理论上会有轻微性能开销(但在绝大多数场景下可忽略不计)。

总结

        适配器模式是 Java 中解决接口不兼容问题的强大工具,它像一座桥梁连接了两个不匹配的世界。对象适配器(使用组合) 是更通用、更推荐的方式。在需要集成旧系统、使用第三方库或统一不同接口时,适配器模式能显著提高代码的复用性、灵活性和可维护性。理解其核心思想(转换接口)和两种实现方式的区别(组合 vs 继承)是应用好该模式的关键。

http://www.dtcms.com/a/278617.html

相关文章:

  • PHP password_get_info() 函数
  • 第一章 uniapp实现兼容多端的树状族谱关系图,创建可缩放移动区域
  • 商城系统的架构与功能模块
  • flink 中配置hadoop 遇到问题解决
  • 用Python向PDF添加文本:精确插入文本到PDF文档
  • vue3+uniapp 使用vue-plugin-hiprint中实现打印效果
  • Triton Inference Server 架构与前后处理方案梳理
  • 打破空间边界!Nas-Cab用模块化设计重构个人存储逻辑
  • JAVA进阶--JVM
  • 设备发出、接收数据帧的工作机制
  • 无人机迫降模式模块运行方式概述!
  • 掉线监测-tezos rpc不能用,改为残疾网页监测
  • .net winfrom 获取上传的Excel文件 单元格的背景色
  • 深入浅出Kafka Producer源码解析:架构设计与编码艺术
  • 创客匠人:创始人 IP 打造的破局点,藏在 “小而精” 的需求里
  • React源码3:update、fiber.updateQueue对象数据结构和updateContainer()中enqueueUpdate()阶段
  • 分布式系统中设计临时节点授权的自动化安全审计
  • postgreSQL的sql语句
  • 时序预测 | Pytorch实现CNN-LSTM-KAN电力负荷时间序列预测模型
  • 2025 春秋杯夏季个人挑战赛 Web
  • lesson13:Python的datetime模块
  • 登录校验与异常处理(web后端笔记第三期)
  • NAT原理与实验指南:网络地址转换技术解析与实践
  • 中国AI应用“三分天下”:国企成主力、中小企偏订阅、C端仍在观望
  • 使用axios向服务器请求信息并渲染页面
  • TCP心跳机制详解
  • 【Linux系统】进程切换 | 进程调度——O(1)调度队列
  • 如何在服务器上运行一个github项目
  • VMware 虚拟机 Ubuntu 无法主机与虚拟机之间复制粘贴的详细解决方案
  • ZLMediaKit流媒体服务器:不用docker -java源码部署Linux问题处理