
在软件开发中,我们经常面临一个尴尬的局面:
你接手了一个 10 年前的老系统,里面有一个核心类 OldService,它的方法叫 doSomethingOld()。
现在公司推行新架构,所有服务必须实现一个新的接口 NewInterface,方法叫 process()。
冲突来了:
- 你不能改老代码(没人敢动,怕崩)。
- 你必须用新接口。
- 这两个接口长得完全不一样。
怎么办?把老代码重写一遍?那估计你也得离职了。
最优雅的办法是:加一个适配器。就像你出国旅游,买个插头转换器一样。
🔌 一、技术分析:偷天换日
适配器模式的核心思想是:将一个类的接口,转换成客户期望的另一个接口。
它让原本因为接口不兼容而不能在一起工作的类,可以协同工作。
1. 核心角色
- Target (目标接口) : 客户期待的接口(比如
Type-C接口)。 - Adaptee (被适配者) : 那个需要被兼容的老家伙(比如
Lightning接口)。 - Adapter (适配器): 那个转换头。它实现了 Target 接口,并在内部持有一个 Adaptee 的引用。
2. 两种实现方式
- 类适配器 (Class Adapter) : 通过继承来实现。
class Adapter extends OldService implements NewInterface- 缺点: Java 是单继承,这占用了继承坑位,不太灵活。
- 对象适配器 (Object Adapter) : 通过组合 来实现。(推荐)
class Adapter implements NewInterface { private OldService old; ... }- 优点: 更加灵活,符合"组合优于继承"原则。
✈️ 二、故事场景:出国旅游的"转换插头"
为了搞懂适配器,我们将 代码对接 比作 出国充电。
1. 场景设定
- 你的电脑 (Client) : 是一台中国产的联想,插头是 三孔扁头。
- 酒店插座 (Adaptee) : 是一家美国酒店,墙上的插孔是 两孔圆头。
- 冲突: 电脑插不进去,没法充电。
2. 解决方案 (Adapter)
你不会因为插不进去就把酒店墙砸了(重构老系统),也不会把电脑插头剪了(修改客户端)。
你会买一个 "中美转换头"。
- 转换头的外观 (Target) : 它是 两孔圆头 的(能插进墙里)。
- 转换头的屁股 (Adaptee) : 它有个 三孔扁头 的插口(能接你的电脑)。
- 内部逻辑: 转换头把墙里的电引出来,传给你的电脑。
3. 代码演示
java
// 1. 目标接口:新系统只认 Type-C
interface TypeC {
void connectTypeC();
}
// 2. 被适配者:老手机是 Lightning 接口
class IPhone {
public void connectLightning() {
System.out.println("正在使用 Lightning 充电...");
}
}
// 3. 适配器:把 Lightning 伪装成 Type-C
class LightningAdapter implements TypeC {
private IPhone iPhone; // 持有老对象的引用 (组合)
public LightningAdapter(IPhone iPhone) {
this.iPhone = iPhone;
}
@Override
public void connectTypeC() {
// 偷梁换柱:表面调 TypeC,实际调 Lightning
System.out.println("适配器开始工作:转接中...");
iPhone.connectLightning();
}
}
// 4. 使用
public class User {
public static void main(String[] args) {
IPhone oldPhone = new IPhone();
// 这里的 adapter 看起来像个 TypeC,其实内部包着 Lightning
TypeC adapter = new LightningAdapter(oldPhone);
adapter.connectTypeC();
}
}
🚀 三、实战:Spring MVC 的"万能遥控器"
你可能没手写过适配器,但你天天都在用。
1. Spring MVC 的 HandlerAdapter
在 Spring MVC 中,你写的 Controller 有很多种写法:
@RequestMapping(最常用的)- 实现
Controller接口 (古老写法) - 实现
HttpRequestHandler接口 (处理静态资源)
DispatcherServlet (核心控制器) 很头大:"你们这帮 Controller 长得都不一样,我怎么调用你们?"
于是 Spring 设计了 HandlerAdapter:
RequestMappingHandlerAdapter: 专门适配注解写的 Controller。SimpleControllerHandlerAdapter: 专门适配实现了接口的 Controller。
DispatcherServlet 只需要调用 adapter.handle(),适配器内部会去调用你那个千奇百怪的 Controller 方法。
2. 日志门面 (SLF4J)
你项目中可能同时用了 Log4j、Logback、JDK Logging。
SLF4J 就是一个巨大的适配器集合。它让你用统一的代码 log.info(),底层自动适配到各种不同的日志实现上。
🥊 四、易混淆模式对比
这三个模式结构都是 "Wrapper" (包装器),但意图完全不同:
| 模式 | 意图 | 形象比喻 | 接口变化 |
|---|---|---|---|
| 装饰器 (Decorator) | 增强功能 | 钢铁侠战衣 | 接口不变 |
| 代理 (Proxy) | 控制访问 | 明星经纪人 | 接口不变 |
| 适配器 (Adapter) | 兼容接口 | 电源转换头 | 接口变了 |
- 装饰器: 我还是我,但我变强了。
- 代理: 你以为是我,其实是我的替身。
- 适配器: 我原本不是这个,但我伪装成了这个。
🎯 五、总结:没有什么是不兼容的
适配器模式的本质是 "妥协"。
当新旧世界发生冲突,而我们又无力改变世界(老代码)时,适配器就是那座桥梁。它让两个原本老死不相往来的类,能够握手言和。