结构型模式:适配器模式

什么是适配器模式?

适配器模式(Adapter Pattern)是一种常用的结构型设计模式,它的主要作用是将一个类的接口转换成客户端期望的另一个接口。就像现实生活中的各种转接头一样,适配器模式使得原本因接口不兼容而无法一起工作的类能够协同合作。

想象一下,你有一个美国制造的电器,插头是两孔扁头,但你在中国旅行,插座是三孔。这时,你需要一个电源转换器(适配器)来解决这个问题。在软件设计中,适配器模式正是解决这类"接口不匹配"问题的优雅解决方案。

适配器模式也常被称为包装器(Wrapper)模式,因为它就像一个包装纸,将原本不兼容的接口"包装"起来,使其能与目标接口兼容。

适配器模式的类型

适配器模式主要有两种实现方式:

1. 类适配器

类适配器通过多重继承(在Java中通过继承被适配类并实现目标接口)实现适配。类适配器使用的是继承机制。

2. 对象适配器

对象适配器通过组合方式实现适配,即在适配器中持有被适配对象的实例。对象适配器使用的是组合机制。

适配器模式的结构

下面是适配器模式的UML类图,它清晰地展示了这种设计模式的结构:

适配器模式UML类图

在这个结构中,Target(目标接口)是客户端所期望的接口,Adaptee(被适配者)是需要被适配的类或接口,而Adapter(适配器)则是将Adaptee转换成Target的类。

适配器模式的基本实现

对象适配器模式实现

对象适配器使用组合方式,将被适配的类的实例包装在适配器中:

// 目标接口:客户端期望使用的接口
public interface Target {void request();  // 客户端期望调用的方法
}// 被适配的类:已经存在的、接口不兼容的类
public class Adaptee {// 被适配类的方法与目标接口不兼容public void specificRequest() {System.out.println("适配者的特殊请求方法");}
}// 对象适配器类:通过组合方式包含被适配对象
public class ObjectAdapter implements Target {// 持有一个被适配类的引用private Adaptee adaptee;// 通过构造函数注入被适配对象public ObjectAdapter(Adaptee adaptee) {this.adaptee = adaptee;}// 实现目标接口的方法,在内部调用被适配对象的方法@Overridepublic void request() {System.out.println("对象适配器: 转换请求");// 调用被适配对象的方法完成真正的功能adaptee.specificRequest();}
}// 客户端代码
public class Client {public static void main(String[] args) {// 创建被适配对象Adaptee adaptee = new Adaptee();// 创建适配器对象,将被适配对象传入Target adapter = new ObjectAdapter(adaptee);System.out.println("客户端通过适配器调用请求...");// 客户端通过目标接口调用方法,实际上最终会调用被适配对象的方法adapter.request();}
}

在这个对象适配器实现中,我们定义了一个Target接口作为客户端期望使用的接口,而Adaptee是一个已经存在但接口不兼容的类。ObjectAdapter充当适配器角色,它实现了Target接口,同时在内部持有一个Adaptee实例。当客户端调用适配器的request()方法时,适配器会将调用转发给AdapteespecificRequest()方法,从而实现接口的适配。这样,客户端就能够通过目标接口间接使用被适配类的功能,而不必关心它们之间的接口差异。

类适配器模式实现

类适配器使用继承方式,同时继承被适配类并实现目标接口:

// 目标接口:客户端期望使用的接口
public interface Target {void request();  // 客户端期望调用的方法
}// 被适配的类:已经存在的、接口不兼容的类
public class Adaptee {// 被适配类的方法与目标接口不兼容public void specificRequest() {System.out.println("适配者的特殊请求方法");}
}// 类适配器:通过继承被适配类并实现目标接口
public class ClassAdapter extends Adaptee implements Target {// 实现目标接口的方法@Overridepublic void request() {System.out.println("类适配器: 转换请求");// 直接调用父类(被适配类)的方法specificRequest();}
}// 客户端代码
public class Client {public static void main(String[] args) {// 使用类适配器Target adapter = new ClassAdapter();System.out.println("客户端通过适配器调用请求...");// 客户端通过目标接口调用方法adapter.request();}
}

类适配器与对象适配器的主要区别在于实现方式。类适配器通过继承Adaptee类,直接获得了被适配类的方法,而无需像对象适配器那样持有被适配对象的引用。ClassAdapter同时继承了Adaptee类并实现了Target接口,当客户端调用request()方法时,适配器可以直接调用继承自AdapteespecificRequest()方法。类适配器的优点是实现更加简洁,但由于Java只支持单继承,这种方式会受到继承体系的限制,灵活性不如对象适配器。

实际应用示例:电源适配器

让我们用一个现实世界中的例子——电源适配器——来展示适配器模式的应用:

// 美国标准电源接口(110V)
interface USPowerSource {void supplyPowerAt110V();  // 提供110V电力的方法
}// 欧洲标准电源接口(220V)
interface EUPowerSource {void supplyPowerAt220V();  // 提供220V电力的方法
}// 美国电源实现
class USPowerSupply implements USPowerSource {@Overridepublic void supplyPowerAt110V() {System.out.println("提供110V的电力");}
}// 欧洲电源实现
class EUPowerSupply implements EUPowerSource {@Overridepublic void supplyPowerAt220V() {System.out.println("提供220V的电力");}
}// 电子设备接口(期望220V)
interface ElectronicDevice {void powerOn();  // 设备开机方法
}// 欧洲电子设备(需要220V电源)
class EuropeanDevice implements ElectronicDevice {private EUPowerSource powerSource;  // 依赖欧洲标准电源public EuropeanDevice(EUPowerSource powerSource) {this.powerSource = powerSource;}@Overridepublic void powerOn() {System.out.println("欧洲设备启动中...");// 使用欧洲标准电源powerSource.supplyPowerAt220V();System.out.println("欧洲设备工作正常!");}
}// 电源适配器:将110V转为220V(对象适配器模式)
class PowerAdapter implements EUPowerSource {private USPowerSource usPowerSource;  // 持有美国电源对象public PowerAdapter(USPowerSource usPowerSource) {this.usPowerSource = usPowerSource;}// 实现欧洲电源接口方法@Overridepublic void supplyPowerAt220V() {System.out.println("适配器转换中: 110V -> 220V");// 调用美国电源方法usPowerSource.supplyPowerAt110V();System.out.println("电压转换完成,输出220V");}
}// 测试代码
public class PowerAdapterDemo {public static void main(String[] args) {// 在美国使用欧洲设备System.out.println("=== 在美国使用欧洲电器 ===");// 创建美国电源USPowerSource usPower = new USPowerSupply();// 创建适配器(将美国电源适配为欧洲电源)EUPowerSource adapter = new PowerAdapter(usPower);// 创建欧洲设备并使用适配器供电ElectronicDevice europeanDevice = new EuropeanDevice(adapter);// 启动设备europeanDevice.powerOn();System.out.println("\n=== 在欧洲使用欧洲电器(无需适配器)===");// 欧洲电源EUPowerSource euPower = new EUPowerSupply();// 直接使用欧洲电源ElectronicDevice deviceInEurope = new EuropeanDevice(euPower);deviceInEurope.powerOn();}
}

运行结果

=== 在美国使用欧洲电器 ===
欧洲设备启动中...
适配器转换中: 110V -> 220V
提供110V的电力
电压转换完成,输出220V
欧洲设备工作正常!=== 在欧洲使用欧洲电器(无需适配器)===
欧洲设备启动中...
提供220V的电力
欧洲设备工作正常!

这个例子模拟了现实世界中的电源适配器场景。我们有美国标准的110V电源和欧洲标准的220V电源,而欧洲电子设备需要220V电源才能正常工作。当我们在美国(只有110V电源)使用欧洲设备时,需要一个电源适配器来进行转换。适配器PowerAdapter在内部调用美国电源的方法,然后进行必要的转换,最终提供欧洲设备所需的220V电源。这样,欧洲设备就可以通过适配器在美国使用了。而在欧洲使用欧洲设备时,由于电源标准匹配,就不需要适配器了。

这个例子非常直观地展示了适配器的作用:让不兼容的接口(110V和220V)能够协同工作,就像现实中的电源转换器一样。

实际应用示例:旧系统集成

在企业应用中,系统集成是适配器模式的一个典型应用场景。下面我们来看一个旧系统集成的例子:

// 旧的用户信息系统接口
class LegacyUserSystem {// 旧系统返回格式化的字符串public String fetchUserData(String userId) {// 模拟从旧系统获取用户数据,格式为:USER:ID:姓名:性别:年龄:地址return String.format("USER:%s:张三:男:30:北京", userId);}
}// 新系统期望的用户模型
class User {private String id;        // 用户IDprivate String name;      // 用户姓名private String gender;    // 性别private int age;          // 年龄private String address;   // 地址// 构造函数public User(String id, String name, String gender, int age, String address) {this.id = id;this.name = name;this.gender = gender;this.age = age;this.address = address;}// 重写toString方法,方便输出用户信息@Overridepublic String toString() {return "User{" +"id='" + id + '\'' +", name='" + name + '\'' +", gender='" + gender + '\'' +", age=" + age +", address='" + address + '\'' +'}';}// Getters 和 Setters省略
}// 新的用户服务接口(新系统期望的接口)
interface UserService {User getUser(String userId);     // 获取用户信息void saveUser(User user);        // 保存用户信息
}// 适配器:将旧系统集成到新系统(对象适配器模式)
class UserSystemAdapter implements UserService {private LegacyUserSystem legacySystem;  // 持有旧系统的引用public UserSystemAdapter(LegacyUserSystem legacySystem) {this.legacySystem = legacySystem;}// 实现新接口的获取用户方法@Overridepublic User getUser(String userId) {// 从旧系统获取数据String userData = legacySystem.fetchUserData(userId);// 解析旧系统返回的字符串数据并转换为User对象String[] parts = userData.split(":");if (parts.length < 5) {throw new RuntimeException("无效的用户数据格式");}String id = parts[1];String name = parts[2];String gender = parts[3];int age = Integer.parseInt(parts[4]);String address = parts[5];// 返回新系统能理解的用户对象return new User(id, name, gender, age, address);}// 实现新接口的保存用户方法@Overridepublic void saveUser(User user) {// 这里可以实现将新系统User对象保存到旧系统的逻辑System.out.println("将用户保存到旧系统:" + user);// 在实际应用中,应当调用旧系统的API来保存用户}
}// 新系统的用户管理类
class UserManager {private UserService userService;  // 依赖用户服务接口public UserManager(UserService userService) {this.userService = userService;}// 显示用户信息的方法public void displayUserInfo(String userId) {try {// 通过用户服务获取用户信息User user = userService.getUser(userId);System.out.println("用户信息:" + user);} catch (Exception e) {System.out.println("获取用户信息失败:" + e.getMessage());}}
}// 测试代码
public class SystemIntegrationDemo {public static void main(String[] args) {// 创建旧系统实例LegacyUserSystem legacySystem = new LegacyUserSystem();// 创建适配器,将旧系统适配到新接口UserService adapter = new UserSystemAdapter(legacySystem);// 新系统使用适配后的服务UserManager userManager = new UserManager(adapter);// 通过新系统接口访问旧系统数据System.out.println("=== 使用适配器访问旧系统 ===");userManager.displayUserInfo("12345");}
}

在这个系统集成的例子中,我们有一个旧的用户信息系统LegacyUserSystem,它以字符串格式返回用户数据。而新系统需要使用结构化的User对象。为了解决这个接口不匹配的问题,我们创建了一个适配器UserSystemAdapter,它实现了新系统期望的UserService接口,同时在内部调用旧系统的API。

适配器负责将旧系统返回的字符串数据解析并转换为新系统需要的User对象。这样,新系统的UserManager就可以通过UserService接口与适配器交互,而不需要知道后面实际上是旧系统在提供数据。通过这种方式,适配器模式使得系统集成变得优雅且松耦合,新系统不需要直接适应旧系统的接口,而是通过适配器间接地使用旧系统的功能。

适配器模式在Java标准库中的应用

Java标准库中有许多适配器模式的例子,了解这些例子有助于我们理解适配器模式在实际开发中的应用:

Java的InputStreamReaderOutputStreamWriter类就是典型的适配器模式应用。InputStreamReader将字节流(InputStream)适配为字符流(Reader),解决了字节与字符的转换问题。同样,OutputStreamWriter将字节输出流(OutputStream)适配为字符输出流(Writer)。这样,开发者就可以用统一的字符流接口处理不同编码的输入输出,而不必关心底层的字节处理细节。

Arrays.asList()方法也是一个适配器的例子,它将数组适配为List集合,使数组可以使用集合的方法。通过这个适配器,我们可以将一个固定长度的数组转换为一个List接口的对象,从而能够使用集合框架提供的丰富功能。

另外,Collections.list()将旧式的Enumeration适配为现代的List集合,这是为了兼容早期Java版本的代码而设计的适配器。Java XML绑定API中的XmlAdapter则是在XML数据与Java对象之间进行转换的适配器,它使得XML序列化和反序列化过程更加灵活可控。

适配器模式的优缺点

优点

优点说明
增加了类的透明性客户端通过目标接口与适配器交互,不需要了解适配器背后的实现细节
提高了类的复用性通过适配器,原本不兼容的类可以在新环境中得到复用
灵活性和可扩展性可以引入更多适配器支持更多类型的适配者,系统更易于扩展
遵循开闭原则无需修改现有代码,通过添加适配器来满足新需求
结构清晰适配器的职责明确,系统结构清晰易于理解和维护

缺点

缺点说明
增加系统复杂度引入适配器会增加系统中的类和间接层,使系统略微复杂化
可能需要修改多个适配器当适配者接口发生变化时,所有相关适配器可能都需要更新
可能导致性能损失通过中间层转换可能带来轻微的性能损失
调试复杂度增加当出现问题时,可能需要调试适配层而非业务层,增加排错难度

最后的一丢丢总结

适配器模式是一种强大的结构型设计模式,它能够将不兼容的接口转换成客户端期望的接口,让原本无法一起工作的类能够协同工作。通过适配器模式,我们可以集成新系统和遗留系统,重用现有的类,使第三方库和现有系统无缝协作,以及在不修改现有代码的情况下满足新的接口需求。

适配器模式有两种主要实现方式:类适配器(通过继承)和对象适配器(通过组合)。在实际应用中,对象适配器更为常用,因为它更加灵活且符合"组合优于继承"的设计原则。虽然适配器模式增加了一定的间接性和复杂性,但它提供的接口转换能力使得系统更加灵活、可扩展,特别是在系统集成和演化过程中,适配器模式能够发挥重要作用。

当你面临接口不兼容的问题,或需要集成多个系统时,不妨考虑使用适配器模式。它就像现实生活中的转接头一样,能够让不兼容的部分和谐地工作在一起,让系统更加灵活和可维护。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/78099.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI Agent认知框架(ReAct、函数调用、计划与执行、自问自答、批判修正、思维链、思维树详解和对比,最后表格整理总结

以下是主流AI Agent认知框架的详细说明、对比及表格总结&#xff1a; 1. 各认知框架详解 (1) ReAct (Reasoning Action) 定义&#xff1a;结合推理&#xff08;Reasoning&#xff09;和行动&#xff08;Action&#xff09;的循环过程。核心机制&#xff1a; 模型先推理&…

特征存储的好处:特征存储在机器学习开发中的优势

随着企业寻求提升机器学习生产力和运营能力 (MLOps),特征存储 (Feature Store) 的普及度正在迅速提升。随着 MLOps 技术的进步,特征存储正成为机器学习基础设施的重要组成部分,帮助企业提升模型的性能和解释能力,并加速新模型与生产环境的集成。这些存储充当集中式存储库,…

SPRING-AI 官方事例

springAI 关于最近看了很多SpringAi&#xff0c;阅读很多代码都感觉特别陌生 SpringAI依赖的springBoot版本都是3.3以上, 以及很多SpringAi都是依赖JDK版本最低17, 并且出现了很多新关键字例如 var,record 等写法, 烟花缭乱得lambda 表达式&#xff0c; 到处都是使用build 构…

Visual Studio Code 使用tab键往左和往右缩进内容

使用VSCode写东西&#xff0c;经常遇到多行内容同时缩进的情况&#xff0c;今天写文档的时候就碰到&#xff0c;记录下来&#xff1a; 往右缩进 选中多行内容&#xff0c;点tab键&#xff0c;会整体往右缩进&#xff1a; 往左缩进 选中多行内容&#xff0c;按shifttab&am…

机器学习(7)——K均值聚类

文章目录 1. K均值&#xff08;K-means&#xff09;聚类是什么算法&#xff1f;2. 核心思想2. 数学目标3. 算法步骤3.1. 选择K个初始质心&#xff1a;3.2.迭代优化3.3. 重复步骤2和步骤3&#xff1a; 4. 关键参数5. 优缺点6. 改进变种7. K值选择方法8. Python示例9. 应用场景10…

爬虫案例-爬取某企数据

文章目录 1、准备要爬取企业名称数据表2、爬取代码3、查看效果 1、准备要爬取企业名称数据表 企业名称绍兴市袍江王新国家庭农场绍兴市郑杜粮油专业合作社绍兴市越城区兴华家庭农场绍兴市越城区锐意家庭农场绍兴市越城区青甸畈家庭农场绍兴市袍江王新国家庭农场绍兴市袍江月明…

足球 AI 智能体技术解析:从数据采集到比赛预测的全链路架构

一、引言 在足球运动数字化转型的浪潮中&#xff0c;AI 智能体正成为理解比赛、预测赛果的核心技术引擎。本文从工程实现角度&#xff0c;深度解析足球 AI 的技术架构&#xff0c;涵盖数据采集、特征工程、模型构建、实时计算到决策支持的全链路技术方案&#xff0c;揭示其背后…

怎么配置一个kubectl客户端访问多个k8s集群

怎么配置一个kubectl客户端访问多个k8s集群 为什么有的客户端用token也访问不了k8s集群&#xff0c;因为有的是把~/.kube/config文件&#xff0c;改为了~/.kube/.config文件&#xff0c;文件设置成隐藏文件了。 按照kubectl的寻找配置的逻辑&#xff0c;kubectl找不到要访问集群…

[QMT量化交易小白入门]-四十六、年化收益率118%的回测参数,如何用贪心算法挑选50个两两相关性最小的ETF组合

本专栏主要是介绍QMT的基础用法,常见函数,写策略的方法,也会分享一些量化交易的思路,大概会写100篇左右。 QMT的相关资料较少,在使用过程中不断的摸索,遇到了一些问题,记录下来和大家一起沟通,共同进步。 文章目录 相关阅读准备工作安装所需库导入所需模块下载所有ETF数…

几何编码:启用矢量模式地理空间机器学习

在 ML 模型中使用点、线和多边形&#xff0c;将它们编码为捕捉其空间属性的向量。 自地理信息系统 (GIS) 诞生之初&#xff0c;“栅格模式”和“矢量模式”之间就存在着显著的区别。在栅格模式下&#xff0c;数据以值的形式呈现在规则的网格上。这包括任何形式的图像&#xff0…

Leetcode98、230:二叉搜索树——递归学习

什么是二叉搜索树&#xff1a;右子树节点 > 根节点 > 左子树节点&#xff0c; 二叉搜索树中的搜索&#xff0c;返回给定值val所在的树节点 终止条件为传进来的节点为空、或者节点的值 val值&#xff0c;返回这个节点&#xff1b; 单程递归逻辑&#xff1a;定义一个resu…

每天学一个 Linux 命令(30):cut

​​可访问网站查看,视觉品味拉满: http://www.616vip.cn/30/index.html cut 命令用于从文件或输入流中提取文本的特定部分(如列、字符或字节位置)。它常用于处理结构化数据(如 CSV、TSV)或按固定格式分割的文本。以下是详细说明和示例: 命令格式 cut [选项] [文件...]…

Tauri 2.3.1+Leptos 0.7.8开发桌面应用--Sqlite数据库选中数据的表格输出

在前期工作的基础上&#xff08;Tauri 2.3.1Leptos 0.7.8开发桌面应用--Sqlite数据库的写入、展示和选择删除_tauri leptos sqlite 选择删除-CSDN博客&#xff09;&#xff0c;实现将选中的数据实时用表格展示出来&#xff0c;效果如下&#xff1a; 1. 后台invoke调用命令 Tau…

使用Tauri 2.3.1+Leptos 0.7.8开发桌面小程序汇总

近期断断续续学习了Rust编程&#xff0c;使用Tauri 2.3.1Leptos 0.7.8开发了一个自用的桌面小程序。Win10操作系统&#xff0c;使用VS Code及rust analyzer插件搭建的开发环境&#xff0c;后期开始使用Roo Code绑定DeepSeek API 辅助编程&#xff0c;对我这个初学者编程帮助很大…

考研英一学习笔记

2024 年全国硕士研究生招生考试 英语&#xff08;一&#xff09;试题 &#xff08;科目代码&#xff1a;201&#xff09; Section Ⅰ Use of English Directions: Read the following text. Choose the best word(s) for each numbered blank and mark A, B, C or D on the ANS…

【技术笔记】Cadence实现Orcad与Allegro软件交互式布局设置

【技术笔记】Cadence实现Orcad与Allegro软件交互式布局设置 更多内容见专栏&#xff1a;【硬件设计遇到了不少问题】、【Cadence从原理图到PCB设计】 在做硬件pcb设计的时候&#xff0c;原理图选中一个元器件&#xff0c;希望可以再PCB中可以直接选中。 为了达到原理图和PCB两两…

卷积神经网络(CNN)详解

文章目录 引言1.卷积神经网络&#xff08;CNN&#xff09;的诞生背景2.卷积神经网络&#xff08;CNN&#xff09;介绍2.1 什么是卷积神经网络&#xff1f;2.2 卷积神经网络&#xff08;CNN&#xff09;的基本特征2.2.1 局部感知&#xff08;Local Connectivity&#xff09;2.2.…

8051单片机所有Keil C51汇编伪指令和C语言关键字大全

8051单片机所有Keil C51汇编伪指令和C语言关键字大全 作者将狼才鲸创建日期2025-04-21 CSDN阅读地址&#xff1a;8051单片机所有Keil汇编伪指令和C语言关键字的详细解释 8051单片机所有Keil汇编伪指令和C语言关键字的详细解释&#xff0c;在Keil已安装文件夹D:\Keil_v5\C51\H…

机器视觉的智能手机屏贴合应用

在智能手机制造领域&#xff0c;屏幕贴合工艺堪称"微米级的指尖芭蕾"。作为影响触控灵敏度、显示效果和产品可靠性的关键工序&#xff0c;屏幕贴合精度直接决定了用户体验。传统人工对位方式已无法满足全面屏时代对极窄边框和超高屏占比的严苛要求&#xff0c;而Mast…