这是 2020 年 3 月整理的设计模式专题第四篇。适配器模式是结构型模式里最贴近生活的——手机充电器的电源适配器就是活生生的例子。本文用它讲清适配器的本质:如何在不改源码的情况下,让接口不兼容的类协同工作。
简单说明
适配器模式(Adapter Pattern):将一个类的接口转换成客户期望的另一个接口,使原本不兼容的接口可以兼容一起工作。
- 适配器模式适合工作在已经存在的类的方法和需求不匹配的时候;
- 适配器模式不是软件设计阶段考虑的设计模式,而是随着业务的发展,不同产品、不同厂商造成功能类似而接口不同情况下的解决方案。
核心思想:不改动已有类的代码,也不改动客户端代码,中间加一层”转换器”。
简单代码实现
模拟将 220V 交流电转换为 5V 直流电的电源适配器。
/**
* 描述:220V 交流电(被适配者)
*/
public class AC220 {
public int outPut220V() {
return 220;
}
}
/**
* 描述:5V 直流(适配目标)
*/
public interface DC5 {
int outPut5V();
}
/**
* 描述:电源适配器(对象适配器实现)
*/
public class PowerAdapter implements DC5 {
AC220 ac220 = new AC220();
@Override
public int outPut5V() {
return ac220.outPut220V() / 44;
}
}
/**
* 描述:适配器测试类
*/
public class AdapterTest {
public static void main(String[] args) {
DC5 dc5 = new PowerAdapter();
System.out.println(dc5.outPut5V());
}
}
角色对应:
AC220:被适配者(Adaptee),220V 交流电;DC5:目标接口(Target),客户端期望的 5V 直流;PowerAdapter:适配器(Adapter),内部持有AC220,把 220V “转换”成 5V。
对象适配器 vs 类适配器
上面的实现是对象适配器:适配器组合被适配者(PowerAdapter 内部 new AC220())。
类适配器则通过继承实现:适配器继承 AC220,直接调用 super 的方法,而不用在适配器中 new。
/**
* 描述:类适配器:继承 AC220,直接调用父类方法
*/
public class PowerAdapter extends AC220 implements DC5 {
@Override
public int outPut5V() {
return outPut220V() / 44;
}
}
| 对比项 | 对象适配器 | 类适配器 |
|---|---|---|
| 实现方式 | 组合(持有被适配者实例) | 继承(extends 被适配者) |
| 耦合度 | 低,更灵活 | 高,受继承限制 |
| 应用 | 更常用(优先组合) | 较少用 |
适用场景与优缺点
适用场景:
- 已有类的接口与目标接口不匹配,又不想修改已有类;
- 多个不同厂商/系统的功能类似但接口不同,需要统一(如不同数据库驱动、不同支付渠道);
- 系统扩展阶段,接入历史遗留系统。
优点:
- 让两个不兼容的接口协同工作,无需修改已有类,符合开闭原则;
- 提高类的透明性和复用性(已有类可以被复用到新接口下);
- 对象适配器通过组合,灵活性好。
缺点:
- 过多使用适配器会让系统结构混乱(满屏 Adapter);
- 类适配器受 Java 单继承限制,只能适配一个被适配者。
现实中的应用
| 场景 | 说明 |
|---|---|
| 电源适配器 | 220V → 5V,最直观的比喻 |
| InputStreamReader / OutputStreamWriter | 字节流 → 字符流的适配器 |
| JDBC 驱动 | 不同数据库方言统一成 JDBC 接口 |
| Spring MVC HandlerAdapter | 适配不同类型的 Controller(@Controller、HttpRequestHandler) |
| 支付系统 | 支付宝/微信/银联统一成一套支付接口 |
小结
- 适配器模式将一个类的接口转换成客户期望的另一个接口,让不兼容的接口协同工作。
- 它是事后补救型模式——不是设计阶段规划出来的,而是系统演进、接口不一致时的解决方案。
- 两种实现:对象适配器(组合,推荐) 与 类适配器(继承)。
- 核心价值:不改已有代码,中间加一层转换器,符合开闭原则。