摘要

本文详细介绍了适配器设计模式,包括其定义、核心思想、角色、结构、实现方式、适用场景及实战示例。适配器模式是一种结构型设计模式,通过将一个类的接口转换成客户端期望的另一个接口,解决接口不兼容问题,提高系统灵活性和可复用性,符合“开闭原则”。文中还探讨了对象适配器和类适配器两种实现方式,以及如何结合策略模式动态选择适配器。

1. 适配器设计模式定义

适配器模式:将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。适配器就是“中间翻译层”,用一个类把“不兼容”的类封装起来,让它们看起来“兼容”,从而可以被别的类调用。

1.1.1. 适配器模式的核心思想

  • 解决接口不兼容问题:通过适配器,将一个类的接口“包装”成另一个接口,供客户端调用。
  • 提高系统的灵活性和可复用性:客户端无需修改现有代码即可调用新的类或组件。
  • 符合“开闭原则”:对扩展开放,对修改关闭。

1.1.2. 📌 核心点总结:

点位

含义

目标接口

客户端期望使用的接口。

适配者(Adaptee)

已有的类(接口不兼容)

适配器(Adapter)

封装 Adaptee,实现 Target 接口,实现“转换”逻辑。

1.1.3. 适配器模式的角色

角色

说明

目标接口 (Target)

客户端期望的接口。客户端通过这个接口与适配器交互。

需要适配的类 (Adaptee)

现有的接口或类,功能符合需求,但接口不兼容。

适配器 (Adapter)

实现目标接口,内部持有需要适配的类的实例,并通过调用其方法来实现目标接口。

2. 适配器设计模式结构

适配器模式包含如下角色:

  • Target:目标抽象类
  • Adapter:适配器类
  • Adaptee:适配者类
  • Client:客户类

适配器模式有对象适配器和类适配器两种实现:

2.1. 对象适配器:

2.2. 类适配器:

2.3. 时序图

3. 适配器设计模式实现方式

3.1. 对象适配器(Object Adapter)

实现方式:适配器类内部通过组合的方式,持有被适配者类(Adaptee)的实例。

特点

  • 适配器实现目标接口(Target),
  • 内部调用被适配者实例的方法实现目标接口的方法,
  • 适配灵活,适配器和被适配者解耦,
  • 适配器可以适配多个被适配者实例。

结构示例

// 目标接口
interface Target {void request();
}// 被适配者
class Adaptee {void specificRequest() {System.out.println("被适配者的具体请求");}
}// 适配器(对象适配器)
class Adapter implements Target {private Adaptee adaptee;public Adapter(Adaptee adaptee) {this.adaptee = adaptee;}@Overridepublic void request() {// 通过调用被适配者的方法完成目标接口功能adaptee.specificRequest();}
}

3.2. 类适配器(Class Adapter)

实现方式:适配器通过继承的方式,同时继承被适配者类(Adaptee),并实现目标接口(Target)。

特点

  • 适配器类是被适配者的子类,
  • 可以直接调用被适配者的受保护或公共方法,
  • 只能适配一个被适配者类(Java单继承限制),
  • 适配器和被适配者耦合度较高。

结构示例

// 目标接口
interface Target {void request();
}// 被适配者
class Adaptee {void specificRequest() {System.out.println("被适配者的具体请求");}
}// 适配器(类适配器)
class Adapter extends Adaptee implements Target {@Overridepublic void request() {// 直接调用父类的方法super.specificRequest();}
}

3.3. 适配器示例总结

特性

对象适配器

类适配器

适配方式

组合(持有被适配者实例)

继承(直接继承被适配者)

灵活性

高,可以动态切换适配对象

低,继承限制,固定继承一个类

耦合度

Java单继承限制

无限制

只能继承一个被适配者类

4. 适配器设计模式适合场景

4.1. ✅ 适合使用适配器设计模式的场景

使用场景

说明

对接多个第三方接口,接口风格不一致

比如接多个风控、支付、短信、物流等服务,不同厂商接口差异很大。→ 使用适配器封装不同厂商接口,统一成系统期望的接口。

封装老旧系统/遗留代码

老系统接口风格与新系统不一致,但又不能修改旧代码。→ 使用适配器包装旧接口,提供符合新接口的使用方式。

统一Controller参数处理逻辑

Spring MVC 中自定义参数解析器 HandlerMethodArgumentResolver,可以视为一种适配器,将 HTTP 请求参数适配成业务对象。

消息中间件适配

Kafka、RabbitMQ、RocketMQ 提供的消息结构不一致,可使用适配器封装统一消费接口。

统一日志、监控、埋点等系统接入方式

不同日志系统(如 Logback、Log4j、ELK)、监控平台(如 Prometheus、SkyWalking)API 不统一,使用适配器将其统一为系统内部日志接口。

兼容不同规则引擎或插件机制

接入 Drools、EasyRules、自研规则引擎,通过适配器统一规则执行接口。

跨平台资源访问

比如统一适配本地文件系统、FTP、OSS、MinIO 等多种文件服务的上传/下载接口。

4.2. ❌ 不适合使用适配器设计模式的场景

场景

原因

接口已经统一,只需调用不同实现

用策略模式或 Spring Bean 多实现注入更合适

功能非常简单,仅调用一行代码

直接调用原类,无需适配

高性能要求场景,不能增加中间层

适配器可能增加调用链层次

适配器维护成本高于直接重构原类

如果可控代码建议重构,而不是套一层适配器

4.3. 📌 适配器设计模式总结

项目

适配器设计模式适用

不适用

是否接口不兼容但需协同工作

✅ 是

❌ 否

是否需要复用现有类且不改代码

✅ 是

❌ 否

是否频繁变更需求接口

❌ 否

✅ 是

是否对性能极度敏感

❌ 否

✅ 是

是否适配类与目标接口差异大

❌ 否

✅ 是

5. 适配器设计模式实战示例

背景:风控系统中,有多个第三方风险评分服务接口(接口不统一),需要统一成系统期望的接口供业务调用。使用适配器模式实现不同第三方服务的适配。

5.1. 场景描述

  • 系统需要调用不同第三方风控服务接口(比如:AlphaRiskServiceBetaRiskService),它们方法名、参数不同。
  • 系统定义统一的风控评分接口RiskScoreService,所有第三方服务通过适配器实现该接口。
  • Spring管理适配器bean,业务直接调用统一接口。

5.2. 定义统一风控评分接口(目标接口)

public interface RiskScoreService {/*** 计算用户的风险评分* @param userId 用户ID* @return 风险评分分数,范围0-100*/int calculateRiskScore(String userId);
}

5.3. 第三方风控服务接口及实现(被适配者)

// 第三方A风险服务,接口不统一
public class AlphaRiskService {public double getUserRisk(String userId) {// 模拟调用第三方接口,返回0.0~1.0的风险概率return Math.random();}
}// 第三方B风险服务,接口不同
public class BetaRiskService {public int fetchRiskLevel(String userId) {// 返回风险等级 1~5,5最高风险return (int)(Math.random() * 5) + 1;}
}

5.4. 适配器实现统一接口

import org.springframework.stereotype.Component;// Alpha适配器,组合方式(对象适配器)
@Component
public class AlphaRiskAdapter implements RiskScoreService {@Autowiredprivate final AlphaRiskService alphaRiskService;@Overridepublic int calculateRiskScore(String userId) {double riskProb = alphaRiskService.getUserRisk(userId);// 将0.0~1.0风险概率转为0~100分return (int)(riskProb * 100);}
}// Beta适配器,组合方式
@Component
public class BetaRiskAdapter implements RiskScoreService {@Autowiredprivate final BetaRiskService betaRiskService;@Overridepublic int calculateRiskScore(String userId) {int riskLevel = betaRiskService.fetchRiskLevel(userId);// 将风险等级1~5映射为0~100分return (riskLevel - 1) * 25;}
}

5.5. 业务服务调用统一接口

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class RiskEvaluationService {@Autowiredprivate final RiskScoreService riskScoreService;// 自定义注解@Qualifier来选择注入哪个适配器。// @Qualifier("")// private final RiskScoreService riskScoreService;public void evaluateUserRisk(String userId) {int score = riskScoreService.calculateRiskScore(userId);System.out.println("用户 " + userId + " 的风险评分为:" + score);// 根据风险评分做后续风控策略处理...}
}

5.6. Spring配置说明

  • 你可以通过Spring配置或自定义注解@Qualifier来选择注入哪个适配器。
  • 或者用策略模式管理多个适配器,根据业务动态选择。

6. 适配器设计模式思考

6.1. 用策略模式管理多个适配器,根据业务动态选择(策略模式 + 适配器模式结合示例)。

下面给你一个策略模式 + 适配器模式结合的示例,用于风控系统中动态选择不同适配器实现。

6.1.1. 设计思路

  • 各个第三方风控服务适配成实现统一接口 RiskScoreService 的适配器。
  • 定义策略上下文 RiskScoreContext,根据业务传入的标识动态选择具体适配器(策略)执行。
  • Spring管理多个适配器Bean,使用 @Qualifier 或自定义注解区分。
  • 业务调用上下文,动态选择适配器执行。

6.1.2. 统一接口(适配器接口)

public interface RiskScoreService {int calculateRiskScore(String userId);
}

6.1.3. 2. 两个适配器实现(对象适配器)

import org.springframework.stereotype.Component;@Component("alphaAdapter")
public class AlphaRiskAdapter implements RiskScoreService {private final AlphaRiskService alphaRiskService = new AlphaRiskService();@Overridepublic int calculateRiskScore(String userId) {double riskProb = alphaRiskService.getUserRisk(userId);return (int) (riskProb * 100);}
}@Component("betaAdapter")
public class BetaRiskAdapter implements RiskScoreService {private final BetaRiskService betaRiskService = new BetaRiskService();@Overridepublic int calculateRiskScore(String userId) {int riskLevel = betaRiskService.fetchRiskLevel(userId);return (riskLevel - 1) * 25;}
}

6.1.4. 策略上下文类,注入所有适配器

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.Map;@Component
public class RiskScoreContext {private final Map<String, RiskScoreService> strategyMap;@Autowiredpublic RiskScoreContext(Map<String, RiskScoreService> strategyMap) {this.strategyMap = strategyMap;}/*** 根据key选择对应的适配器执行* @param strategyKey 适配器标识,如 "alphaAdapter"、"betaAdapter"* @param userId 用户ID* @return 风险评分*/public int calculate(String strategyKey, String userId) {RiskScoreService service = strategyMap.get(strategyKey);if (service == null) {throw new IllegalArgumentException("未知的风险评分策略:" + strategyKey);}return service.calculateRiskScore(userId);}
}

6.1.5. 业务调用示例

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class RiskEvaluationService {private final RiskScoreContext riskScoreContext;@Autowiredpublic RiskEvaluationService(RiskScoreContext riskScoreContext) {this.riskScoreContext = riskScoreContext;}public void evaluateUserRisk(String userId, String strategyKey) {int score = riskScoreContext.calculate(strategyKey, userId);System.out.println("使用策略[" + strategyKey + "],用户" + userId + "风险评分为:" + score);// 这里可根据score做风控决策处理}
}

6.1.6. 测试调用示例

// 假设有Spring Boot主程序启动后,调用如下:@Autowired
RiskEvaluationService evaluationService;public void test() {evaluationService.evaluateUserRisk("user123", "alphaAdapter");evaluationService.evaluateUserRisk("user456", "betaAdapter");
}

6.1.7. 说明

  • Spring会自动将所有实现了RiskScoreService接口的Bean注入到strategyMap中,key为Bean的名称(如alphaAdapterbetaAdapter)。
  • 业务调用时传入策略key,根据key动态选择对应适配器。
  • 这样便实现了“策略模式管理多个适配器,根据业务动态选择”的需求

博文参考

  • 适配器模式(Adapter Pattern) | design-patterns
  • https://refactoringguru.cn/design-patterns/adapter

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

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

相关文章

java 开发中 nps的内网穿透 再git 远程访问 以及第三放支付接口本地调试中的作用

在Java开发中&#xff0c;NPS内网穿透、Git远程访问和第三方支付接口的本地调试结合使用&#xff0c;可以有效提升开发效率和调试能力。以下是它们的具体作用及协作场景&#xff1a; 第一&#xff1a;为什么需要nps内网穿透 1. NPS内网穿透的作用 NPS&#xff08;内网穿透工具…

换ip是换网络的意思吗?怎么换ip地址

在数字化时代&#xff0c;IP地址作为我们在网络世界的"身份证"&#xff0c;其重要性不言而喻。许多人常将"换IP"与"换网络"混为一谈&#xff0c;实际上两者虽有联系却存在本质区别。本文将澄清这一概念误区&#xff0c;并详细介绍多种更换IP地址…

云游戏混合架构

云游戏混合架构通过整合本地计算资源与云端能力&#xff0c;形成了灵活且高性能的技术体系&#xff0c;其核心架构及技术特征可概括如下&#xff1a; 一、混合架构的典型模式 分层混合模式‌ 前端应用部署于公有云&#xff08;如渲染流化服务&#xff09;&#xff0c;后端逻辑…

Docker常用命令操作指南(一)

Docker常用命令操作指南-1 一、Docker镜像相关命令1.1 搜索镜像&#xff08;docker search&#xff09;1.2 拉取镜像&#xff08;docker pull&#xff09;1.3 查看本地镜像&#xff08;docker images&#xff09;1.4 删除镜像&#xff08;docker rmi&#xff09; 二、Docker容器…

软件性能之CPU

性能是个宏大而驳杂话题&#xff0c;从代码&#xff0c;到网络&#xff0c;到实施&#xff0c;方方面面都会涉及到性能问题&#xff0c;网上对性能讲解的文章多如牛毛&#xff0c;从原理到方法再到工具都有详细的介绍&#xff0c;本文虽不能免俗&#xff0c;但期望能从另外一个…

[SC]SystemC在CPU/GPU验证中的应用(三)

SystemC在CPU/GPU验证中的应用(三) 摘要:下面分享50个逐步升级SystemC编程能力的示例及建议的学习路线图。您可以一次一批地完成它们——从前五个基础的例子开始,然后转向channels, TLM, bus models, simple CPU/GPU kernels等等。在每个阶段掌握之后,再进行下一组…

如何设计高效的数据湖架构:存储策略、Schema 演进与数据生命周期管理

本文围绕现代数据湖架构的核心设计理念与实践展开,重点讨论如何高效组织数据存储、支持 Schema 演进与版本管理、实现冷热数据分层存储和生命周期治理,确保数据湖在性能、成本、演进和治理能力上的全面可控。 🧭 一、数据湖架构演进概览 传统数据仓库面对高频更新、Schema…

建筑兔零基础人工智能自学记录101|Transformer(1)-14

Transformer 谷歌提出&#xff0c;一组编码-解码器 可以同时处理&#xff0c;通过位置编码来处理单词 实质是token词语接龙&#xff08;只是有不同的概率&#xff09; token对应向量 Transformer简述 文生图就需要用到transformer黑箱 token 内部层次 中间主要是embedding…

Unity基础学习(十二)Unity 物理系统之范围检测

目录 一、关于范围检测的主要API&#xff1a; 1. 盒状范围检测 Physics.OverlapBox 2. 球形范围检测 Physics.OverlapSphere 3. 胶囊范围检测 Physics.OverlapCapsule 4. 盒状检测 NonAlloc 版 5. 球形检测 NonAlloc 版 6. 胶囊检测 NonAlloc 版 二、关于API中的两个重…

构建安全高效的邮件网关ngx_mail_ssl_module

一、快速上手&#xff1a;最小配置示例 worker_processes auto;mail {server {# 监听 IMAP over TLSlisten 993 ssl;protocol imap;# TLS 协议与密码套件ssl_protocols TLSv1.2 TLSv1.3;ssl_ciphers HIGH:!aNULL:!MD5;# 证书与私钥ssl_…

打卡day41

知识回顾 数据增强卷积神经网络定义的写法batch归一化&#xff1a;调整一个批次的分布&#xff0c;常用与图像数据特征图&#xff1a;只有卷积操作输出的才叫特征图调度器&#xff1a;直接修改基础学习率 卷积操作常见流程如下&#xff1a; 1. 输入 → 卷积层 → Batch归一化层…

MySQL高级查询技巧:分组、聚合、子查询与分页【MySQL系列】

本文将深入探讨 MySQL 高级查询技巧&#xff0c;重点讲解 GROUP BY、HAVING、各种聚合函数、子查询以及分页查询&#xff08;LIMIT 语法&#xff09;的使用。文章内容涵盖实际应用中最常见的报表需求和分页实现技巧&#xff0c;适合有一定 SQL 基础的开发者进一步提升技能。 一…

现代 CSS 高阶技巧:实现平滑内凹圆角的工程化实践

通过 数学计算 CSS mask 复合遮罩 实现的真正几何内凹效果&#xff1a; 背景是一张图片&#xff0c;用来证明中间的凹陷是透明的。 完整代码&#xff1a; app.js import FormPage from "./pages/formPage"; import "./App.css"; const App () > {re…

Qt不同布局添加不同控件

对于这种 不同布局添加不同控件 的情况,可以采用以下几种简化方法: 方法 1:使用 std::pair 或 std::tuple 配对(C++17 推荐) for (auto [layout, widget] : {std::pair{m_layoutMistakeCalibrate,

MySQL 事务解析

1. 事务简介 事务&#xff08;Transaction&#xff09; 是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 经典案例&#xff1…

PyTorch中 torch.utils.data.DataLoader 的详细解析和读取点云数据示例

一、DataLoader 是什么&#xff1f; torch.utils.data.DataLoader 是 PyTorch 中用于加载数据的核心接口&#xff0c;它支持&#xff1a; 批量读取&#xff08;batch&#xff09;数据打乱&#xff08;shuffle&#xff09;多线程并行加载&#xff08;num_workers&#xff09;自…

在MDK中自动部署LVGL,在stm32f407ZGT6移植LVGL-8.4,运行demo,显示label

在MDK中自动部署LVGL&#xff0c;在stm32f407ZGT6移植LVGL-8.4 一、硬件平台二、实现功能三、移植步骤1、下载LVGL-8.42、MDK中安装LVGL-8.43、配置RTE4、配置头文件 lv_conf_cmsis.h5、配置lv_port_disp_template 四、添加心跳相关文件1、在STM32CubeMX中配置TIM7的参数2、使能…

德思特新闻 | 德思特与es:saar正式建立合作伙伴关系

德思特新闻 2025年5月9日&#xff0c;德思特科技有限公司&#xff08;以下简称“德思特”&#xff09;与德国嵌入式系统专家es:saar GmbH正式达成合作伙伴关系。此次合作旨在将 es:saar 的先进嵌入式开发与测试工具引入中国及亚太市场&#xff0c;助力本地客户提升产品开发效率…

fork函数小解

学了好久终于搞懂fork函数的一些作用 1. fork函数作用&#xff1a;用于创建新的子进程 这是fork最根本的功能&#xff0c;在父进程里创建新的子进程、 但是创建新的子进程之后呢&#xff1f; 子进程和父进程的关系是什么样的&#xff1f; 为什么fork得到的子进程返回值为0&am…

opencv(C++) 变换图像与形态学操作

文章目录 使用腐蚀和膨胀图像形态滤波器实现案例使用形态学滤波器对图像进行开运算和闭运算实现案例在灰度图像上应用形态学操作算子形态学梯度(Morphological Gradient)黑帽变换(Black-hat Transform)使用分水岭算法进行图像分割使用 MSER 提取显著区域MSER 检测与可视化使…