适配器设计模式:一个“翻译官”的故事

适配器设计模式:一个"翻译官"的故事

一句话记忆 :两个接口不兼容,中间加个翻译官;客户端只认目标口,适配器把旧货搬。


一、生活比喻:出国旅行带转换头

你带了一个国标插头 的充电器,到了英国,墙上只有英标插座。插不进去,怎么办?

你不需要把充电器拆了重做,也不需要把墙砸了重装,你只需要买一个转换头:

  • 国标插头 → 转换头 → 英标插座

这个转换头就是适配器 。它做了一件很纯粹的事:把"我不认识的接口"翻译成"我认识的接口"。

再比如:

  • 读卡器:SD 卡 → 读卡器 → USB 口
  • 电源适配器:220V 交流 → 适配器 → 笔记本要的 19V 直流
  • 同声传译:你说中文,老外只懂英文,翻译在中间来回转

它们的共同点都是:两边本来没法直接合作,加一层翻译就能合作了。


二、模式三角色:谁在适配谁?

适配器模式里有三个角色,记住这张表就够了:

角色 含义 插座比喻
Target(目标接口) 客户端期望使用的接口 墙上的英标插座
Adaptee(被适配者) 已经存在但接口不兼容的类 你的国标插头
Adapter(适配器) 把 Adaptee 翻译成 Target 转换头

客户端只认 Target,它根本不知道 Adaptee 的存在。适配器在中间"左手拉一个、右手拉一个",把两边接上。


三、代码长什么样?

假设客户端只会调用 play(String audioType, String fileName) 这个目标接口,但手里只有一个只会 playMp4() 的老类。

java 复制代码
// Target:客户端期望的接口
interface MediaPlayer {
    void play(String audioType, String fileName);
}

// Adaptee:已经存在、接口不兼容的老类
class Mp4Player {
    public void playMp4(String fileName) {
        System.out.println("播放 mp4:" + fileName);
    }
}

// Adapter:翻译官,实现 Target,内部持有 Adaptee
class Mp4Adapter implements MediaPlayer {
    private Mp4Player mp4Player = new Mp4Player();

    @Override
    public void play(String audioType, String fileName) {
        if ("mp4".equalsIgnoreCase(audioType)) {
            mp4Player.playMp4(fileName);   // 翻译:play → playMp4
        }
    }
}

// 客户端只认 MediaPlayer
MediaPlayer player = new Mp4Adapter();
player.play("mp4", "变形金刚.mp4");

关键点只有一句:适配器实现目标接口,内部持有被适配者,在目标方法里调用被适配者的方法。

小知识:适配器有两种写法------类适配器 用继承(extends Mp4Player implements MediaPlayer),对象适配器 用组合(上面这种)。实际开发中组合优于继承,对象适配器更常用。


四、SpringMVC 里它在哪?------HandlerAdapter

这是你学 SpringMVC 必须打通的一环。

问题背景 :DispatcherServlet 是前端控制器,它拿到请求后,通过 HandlerMapping 找到了一个"处理器"(Handler)。但问题是------Handler 的写法太多了:

  • 实现 Controller 接口的类
  • 用 @Controller + @RequestMapping 注解的方法
  • 实现 HttpRequestHandler 的类
  • 甚至一个普通方法

DispatcherServlet 不可能为每一种写法都写一套 if-else 去调用,否则每加一种 Controller 写法就要改源码,违反开闭原则。

解决方案 :DispatcherServlet 只认一个统一接口------HandlerAdapter。

复制代码
DispatcherServlet(客户端,只认 HandlerAdapter)
        │
        ▼
   HandlerAdapter(目标接口)
        │
   ┌────┼──────────────┬────────────────────┐
   ▼    ▼              ▼                    ▼
RequestMapping     SimpleController    HttpRequestHandler
HandlerAdapter     HandlerAdapter      Adapter
   │                   │                    │
   ▼                   ▼                    ▼
@Controller 方法    实现 Controller 接口   HttpRequestHandler 实现类

每种 Handler 配一个专属 Adapter,Adapter 负责"怎么调用这个 Handler"。DispatcherServlet 只管调 adapter.handle(request, response, handler),具体怎么调,交给适配器。

这就是为什么 SpringMVC 能"无缝支持"各种 Controller 写法------新增一种 Handler,只需新增一个 HandlerAdapter,DispatcherServlet 一行不用改。


五、怎么记?给你三个记忆钩子

钩子 1:口诀

接口不匹配,中间加一层;

客户端认目标,适配器做翻译。

钩子 2:三问法

遇到任何"这算不算适配器"的场景,问三句:

  1. 谁和谁不兼容?(Target 和 Adaptee)
  2. 谁在中间翻译?(Adapter)
  3. 客户端是不是只认目标接口?(是 → 典型适配器)

钩子 3:SpringMVC 锚点

HandlerMapping 负责"找",HandlerAdapter 负责"调"。

找到的 Handler 千奇百怪,Adapter 负责把它们翻译成 DispatcherServlet 认识的统一调用方式。


六、面试怎么答(30 秒版)

适配器模式用于解决接口不兼容 问题:将一个类的接口转换成客户端期望的另一个接口,使原本无法协同工作的类可以一起工作。它包含目标接口、被适配者和适配器三个角色,适配器通过组合 持有被适配者,在目标方法中转发调用。SpringMVC 中的 HandlerAdapter 就是典型应用:DispatcherServlet 只依赖 HandlerAdapter 接口,不同的 Handler 由对应的 Adapter 调用,从而在不修改 DispatcherServlet 的前提下支持多种 Controller 写法,体现了开闭原则 和解耦。


七、常见坑:别和这两个模式搞混

模式 核心目的 一句话区分
适配器 改接口,让不兼容变兼容 "翻译官,只换说法不改内容"
装饰器 加功能,接口不变 "给咖啡加奶加糖,还是那杯咖啡"
代理 控访问,接口不变 "经纪人挡在你和明星之间"

记忆点:适配器改变的是"接口形状",装饰器和代理改变的是"行为/访问",接口本身不变。


总结成一张卡片:

适配器 = 转换头 = 翻译官

目标接口 ← 适配器 → 被适配者

SpringMVC 锚点:DispatcherServlet 只认 HandlerAdapter,Handler 千变万化靠 Adapter 统一调用。

口诀:不匹配,加一层;认目标,做翻译。

相关推荐
扶尔魔ocy5 小时前
【QT C++】常用设计模式
c++·设计模式
她说..6 小时前
常见设计模式-观察者模式
java·spring boot·spring·观察者模式·设计模式
是2的10次方啊7 小时前
设计模式与生活⑨:一碗胡辣汤,用不着认识十几种香料
设计模式
geovindu1 天前
rust: Semaphore Pattern
开发语言·后端·设计模式·rust·代理模式·企业级信号量模式·同步型模式
是2的10次方啊1 天前
设计模式与生活⑧:快递到了,别一直刷物流
设计模式
raindayinrain2 天前
c++设计模式
开发语言·c++·设计模式
工一木子2 天前
设计模式与生活⑦:你打的车,隔着一个平台
设计模式
电子科技圈2 天前
2026 RISC-V中国峰会观察:编译调试工具链的“在场节奏”,释放了什么信号?
大数据·人工智能·设计模式·软件工程·软件构建·代码规范·risc-v
电子科技圈2 天前
Arm电机控制开发工具哪家强?
arm开发·单片机·嵌入式硬件·设计模式·软件构建