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

设计模式-适配器模式

适配器模式

1. 什么是适配器模式?

想象一下,你有一个欧标的电器插头(比如两孔圆形),但你家的插座是美标的(比如两孔扁平或三孔)。你不能直接把欧标插头插到美标插座里。这时候你需要一个“转换插头”或“适配器”,这个转换插头一端可以接欧标插头,另一端可以插到美标插座上。

适配器模式 就是这样一种设计模式,它的核心思想是:将一个类的接口转换成客户端所期望的另一种接口。 使得原本由于接口不兼容而不能一起工作的类可以协同工作。

它充当两个不兼容接口之间的桥梁。

2. 适配器模式的结构 (主要角色):

  • Target (目标接口): 这是客户端代码期望使用的接口。客户端通过这个接口与适配后的对象进行交互。

  • Adaptee (源接口 / 被适配者): 这是已存在的、但其接口与 Target 接口不兼容的类。它是需要被适配的类。

  • Adapter (适配器): 这是核心角色。它实现了 Target 接口,并且内部持有一个 Adaptee 对象的引用(或者继承 Adaptee 类)。它的任务是将对 Target 接口的调用转换成对 Adaptee 接口的调用。

  • Client (客户端): 使用 Target 接口与适配器进行交互的类。客户端并不知道也不关心它实际使用的是哪个 Adaptee。

3. 适配器模式的两种主要实现方式:

a) 类适配器模式 (Class Adapter Pattern):

  • 实现方式: Adapter 类通过多重继承(在 Java 中通过继承 Adaptee 类并实现 Target 接口)来实现。

  • 结构图示:

    +----------------+       +----------------+
    |    Client      |------>|     Target     | (Interface or Abstract Class)
    +----------------+       +----------------+^| (implements/extends)+----------------+|    Adapter     | (Class)+----------------+^| (extends)+----------------+|    Adaptee     | (Class)+----------------+
  • 特点:

    • 适配器直接继承了 Adaptee 类,因此可以重写 Adaptee 的部分方法

    • 由于 Java 不支持多重类继承,如果 Adaptee 是一个具体的类(而不是接口),并且 Target 也是一个具体的类(通常 Target 是接口),这种方式在 Java 中可以实现(Adapter 继承 Adaptee,实现 Target 接口)。

    • 缺点: 耦合度较高。Adapter 必须是 Adaptee 的子类,这限制了其灵活性。如果 Adaptee 是 final 类,则无法使用类适配器。

b) 对象适配器模式 (Object Adapter Pattern):

  • 实现方式: Adapter 类实现 Target 接口,并且在其内部持有一个 Adaptee 对象的实例引用。所有对 Target 接口的调用都会被委派给这个 Adaptee 实例。

  • 结构图示:

    +----------------+       +----------------+
    |    Client      |------>|     Target     | (Interface or Abstract Class)
    +----------------+       +----------------+^| (implements/extends)+----------------+|    Adapter     | (Class)+----------------+|| (has-a / composition)V+----------------+|    Adaptee     | (Class or Interface)+----------------+
  • 特点:

    • 更灵活: Adapter 和 Adaptee 之间的关系是组合/聚合关系,耦合度相对较低。

    • Adapter 可以适配 Adaptee 类及其所有子类。

    • 可以在运行时动态地改变被适配的对象。

    • 是更常用和推荐的方式。

4. 适配器模式的优缺点:

优点:

  • 提高类的复用性: 可以让现有的、接口不兼容的类在新的环境中使用,而无需修改其源代码。

  • 增加类的透明度(对于客户端): 客户端代码只需要与 Target 接口交互,无需关心具体的 Adaptee 实现。

  • 更好的灵活性和扩展性(特别是对象适配器): 可以很容易地替换或增加新的适配器来适配不同的 Adaptee。

  • 解耦: 将客户端与具体的 Adaptee 实现解耦。

缺点:

  • 过多的使用适配器会使系统变得零碎和复杂: 每引入一个适配器都会增加一个类。

  • 类适配器模式的限制:

    • 只能适配一个具体的 Adaptee 类。

    • 在支持单继承的语言(如 Java)中,如果 Adaptee 本身就是 Target 的一个不兼容的子类,可能会比较棘手。

  • 可能增加代码的间接性: 调用需要通过适配器进行转发,可能会有轻微的性能影响(通常可忽略不计)。

5. 适配器模式的应用场景:

  • 系统需要使用现有的类,而这些类的接口不符合系统的需要时。

  • 想要创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作。 (例如,一个通用的数据转换工具)

  • (对象适配器)需要适配一个类的多个子类时,或者需要在运行时动态选择适配的 Adaptee 时。

  • 在不同模块或系统之间进行集成,而它们定义的接口不一致时。

Java 中的例子:

  • java.io.InputStreamReader 和 java.io.OutputStreamWriter:

    • InputStreamReader 是一个适配器,它将字节输入流 (InputStream - Adaptee) 转换为字符输入流 (Reader - Target)。

    • OutputStreamWriter 是一个适配器,它将字符输出流 (Writer - Target) 转换为字节输出流 (OutputStream - Adaptee)。 它们解决了字节操作和字符操作之间的不兼容问题,并处理了字符编码。

  • java.util.Arrays.asList():

    • 这个方法可以将一个数组 (T[] - Adaptee) 适配成一个 List<T> (List - Target) 接口。

    • 返回的 List 是一个固定大小的列表,它内部仍然依赖于原始数组。

  • SLF4J (Simple Logging Facade for Java) 和其他日志框架 (Logback, Log4j) 的桥接包:

    • 例如 log4j-over-slf4j 或 jul-to-slf4j。这些桥接包充当适配器,将对旧日志 API (如 Log4j 1.x 或 java.util.logging) 的调用,适配到 SLF4J 接口,进而路由到 SLF4J 底层绑定的具体日志实现。

代码示例 (对象适配器模式):

假设我们有一个旧的音频播放器 OldAudioPlayer,它只能播放 .mp3 文件。我们希望我们的新播放器 AudioPlayer (Target) 能够播放 .mp4 和 .vlc 文件,同时也能利用 OldAudioPlayer 来播放 .mp3。

// Adaptee (被适配者)
class OldAudioPlayer {public void playMp3(String fileName) {System.out.println("Playing mp3 file: " + fileName);}
}
​
// Target (目标接口)
interface MediaPlayer {void play(String audioType, String fileName);
}
​
// Adapter (适配器)
class MediaAdapter implements MediaPlayer {OldAudioPlayer oldAudioPlayer; // 持有 Adaptee 的引用
​public MediaAdapter(String audioType) {if (audioType.equalsIgnoreCase("mp3")) {oldAudioPlayer = new OldAudioPlayer();}// 如果需要适配其他类型,可以在这里实例化其他 Adaptee}
​@Overridepublic void play(String audioType, String fileName) {if (audioType.equalsIgnoreCase("mp3")) {oldAudioPlayer.playMp3(fileName);}// 对于其他类型,这个适配器目前不处理,或者可以扩展// else if (audioType.equalsIgnoreCase("vlc")) { ... }// else if (audioType.equalsIgnoreCase("mp4")) { ... }}
}
​
// Client (客户端期望使用的播放器)
class AudioPlayer implements MediaPlayer {MediaAdapter mediaAdapter; // 客户端可以使用适配器
​@Overridepublic void play(String audioType, String fileName) {// 内建支持播放 mp4 (假设)if (audioType.equalsIgnoreCase("mp4")) {System.out.println("Playing mp4 file: " + fileName);}// 使用适配器播放其他格式else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp3")) {// 这里为了简单,直接 new。实际项目中可能通过工厂或依赖注入获取适配器实例if (audioType.equalsIgnoreCase("mp3")) {mediaAdapter = new MediaAdapter("mp3"); // 创建针对 mp3 的适配器mediaAdapter.play(audioType, fileName);} else if (audioType.equalsIgnoreCase("vlc")) {// 假设我们有另一个 AdvancedMediaPlayer 接口和其实现// AdvancedMediaPlayer advancedMediaPlayer = new VlcPlayer();// mediaAdapter = new AdvancedMediaAdapter(advancedMediaPlayer); // 创建另一个适配器// mediaAdapter.play(audioType, fileName);System.out.println("Playing vlc file using a different adapter (concept): " + fileName);}} else {System.out.println("Invalid media. " + audioType + " format not supported");}}
}
​
public class AdapterPatternDemo {public static void main(String[] args) {AudioPlayer audioPlayer = new AudioPlayer();
​audioPlayer.play("mp3", "beyond the horizon.mp3");audioPlayer.play("mp4", "alone.mp4");audioPlayer.play("vlc", "far far away.vlc");audioPlayer.play("avi", "mind me.avi");}
}

总结:

适配器模式是一种非常有用的结构型设计模式,它专注于解决接口不兼容的问题,使得原本无法协同工作的组件能够一起工作。在选择类适配器还是对象适配器时,通常对象适配器因其更高的灵活性而被优先考虑。记住那个“转换插头”的例子,就能很好地理解适配器模式的核心思想了。

http://www.xdnf.cn/news/13289.html

相关文章:

  • 微信小程序 - 手机震动
  • 《P1168 中位数》
  • 期末考试复习总结-《应用程序框架基础》
  • 系统网站首页三种常见布局vue+elementui
  • 【Element Plus】Menu组件:url访问页面时高亮对应菜单栏
  • 板凳-------Mysql cookbook学习 (十--4)
  • 小程序动画性能提升指南:CSS硬件加速与JavaScript动画框架对比
  • CentOS下的运维监控Grafana部署
  • 【HarmonyOS Next之旅】DevEco Studio使用指南(三十二) -> 构建系统生命周期
  • okhttp 实现长连接的完整方案
  • OpenLayers 获取地图状态
  • Docker 安装教程(CentOS 系统)纯新手可入门
  • wordpress后台更新后 前端没变化的解决方法
  • Java异步编程之消息队列疑难问题拆解
  • 2506C++,C++的时间库
  • 搭建本地瓦片地图服务器的完整指南
  • 脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
  • SCAU期末笔记 - 数据分析与数据挖掘题库解析
  • 使用 ML.NET Model Builder 训练机器学习模型进行预测性维护
  • 60天python训练计划----day50
  • 连锁超市冷库节能解决方案:如何实现超市降本增效
  • spring中的ImportSelector接口详解
  • 《高等数学》(同济大学·第7版)第四章第一节不定积分的概念与性质
  • 微软PowerBI考试 PL300-在 Power BI 中设计语义模型 【附练习数据】
  • C++11列表初始化:从入门到精通
  • Python学习(8) ----- Python的类与对象
  • 用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章
  • LangChain 中的文档加载器(Loader)与文本切分器(Splitter)详解《二》
  • OpenGL-什么是软OpenGL/软渲染/软光栅?
  • 【求出100~500之间所有每位数的乘积大于每位数的和的数。】2022-4-16