适配器模式:让不兼容的接口握手言和
说实话,我做了十几年软件架构,最常遇到的一个问题就是——新系统要接入老模块,结果接口对不上。就像你买了个新手机,充电线却是老式的Micro-USB,这时候你需要一个转接头。在软件世界里,这个转接头就是适配器模式。
适配器模式,说白了就是让两个原本不兼容的接口能够协同工作。它不改变现有代码,而是在中间加一层转换。我当年接手一个遗留系统时,就靠这个模式救了一命——老系统的数据接口是XML,新系统只认JSON,中间加个适配器,两边都不用改,完美。
定义与核心思想
适配器模式(Adapter Pattern)属于结构型设计模式。它的定义是:将一个类的接口转换成客户期望的另一个接口。让原本因接口不匹配而无法一起工作的类能够协同工作。
你想想看,这个模式其实很生活化。你去国外旅游,插座是两孔圆头,你的充电器是三孔扁头,这时候你需要一个转换插头。适配器模式就是软件里的转换插头。
核心角色:
- 目标接口(Target):客户期望的接口
- 适配者(Adaptee):需要被适配的现有接口
- 适配器(Adapter):核心转换器,连接Target和Adaptee
- 客户(Client):使用目标接口的调用方
两种实现方式
适配器模式有两种实现方式:类适配器和对象适配器。我个人习惯优先使用对象适配器,原因后面会讲。但两种都得掌握,毕竟面试常考。
类适配器(基于继承)
类适配器通过多重继承实现。适配器同时继承目标接口和适配者类。Java不支持多重继承,所以类适配器在Java里用得少,但在C++中很常见。
// 目标接口
public interface ITarget {
void request();
}
// 适配者类(已有代码,接口不兼容)
public class Adaptee {
public void specificRequest() {
System.out.println("适配者:我是老接口,干的是specificRequest的活");
}
}
// 类适配器:继承适配者,实现目标接口
public class ClassAdapter extends Adaptee implements ITarget {
@Override
public void request() {
// 在request方法里调用specificRequest
System.out.println("类适配器:我来转接");
super.specificRequest();
}
}
// 客户端使用
public class Client {
public static void main(String[] args) {
ITarget target = new ClassAdapter();
target.request(); // 客户端只认识request,实际调用的是specificRequest
}
}
嗯,这里要注意:类适配器因为继承是静态的,所以适配器只能适配一个Adaptee类,不能适配其子类。灵活性差了点。
对象适配器(基于组合)
对象适配器通过组合实现。适配器持有适配者的引用,在目标接口方法中委托给适配者。这是我最推荐的方式。
// 目标接口
public interface ITarget {
void request();
}
// 适配者类(不变)
public class Adaptee {
public void specificRequest() {
System.out.println("适配者:老接口干活了");
}
}
// 对象适配器:持有适配者引用,实现目标接口
public class ObjectAdapter implements ITarget {
private Adaptee adaptee; // 组合方式
public ObjectAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
System.out.println("对象适配器:我来转接");
adaptee.specificRequest(); // 委托给适配者
}
}
// 客户端使用
public class Client {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
ITarget target = new ObjectAdapter(adaptee);
target.request();
}
}
我的经验:对象适配器更灵活。它可以适配Adaptee及其所有子类,因为持有的是父类引用。而且组合比继承更符合「优先使用组合而非继承」的设计原则。我在项目中几乎只用对象适配器。
SVG结构图:适配器模式核心逻辑
优缺点分析
| 维度 | 类适配器 | 对象适配器 |
|---|---|---|
| 实现方式 | 继承(多重继承) | 组合(持有引用) |
| 灵活性 | 低,只能适配一个Adaptee类 | 高,可适配Adaptee及其子类 |
| 代码侵入 | 需要继承Adaptee,可能暴露父类方法 | 完全封装Adaptee,不暴露内部细节 |
| 适用语言 | C++等多继承语言 | 所有面向对象语言 |
| 推荐度 | ⭐(不推荐) | ⭐⭐⭐⭐⭐(强烈推荐) |
避坑指南:我曾经在一个项目中用了类适配器,结果Adaptee的父类方法被意外暴露,导致客户端直接调用了不该调的方法,出了线上bug。从那以后,我坚持用对象适配器。组合比继承安全得多。
应用场景
适配器模式不是万能的,但以下几个场景特别适合:
- 系统升级改造:老系统的接口不能改,新系统要接入。加个适配器,两边都满意。
- 第三方库集成:第三方库的接口和你系统的接口不一致。适配器做翻译官。
- 统一多个相似接口:多个类做类似的事,但接口不同。适配器统一成一套接口。
- 数据格式转换:XML转JSON,CSV转Excel。适配器做数据转换。
举个例子,我做过一个支付系统。对接了微信支付、支付宝、银联,三家接口完全不同。我定义了一个统一的支付接口,然后为每家写一个适配器。客户端只认我的接口,底层随便换支付渠道。这就是适配器模式的经典应用。
什么时候别用适配器?
- 如果接口差异太大,适配器会变得很复杂,不如重构。
- 如果Adaptee的接口不稳定经常变,适配器也得跟着改,维护成本高。
- 如果只是临时用一下,适配器可能过度设计,直接改代码更简单。
总结
适配器模式,说白了就是软件世界的转接头。它不改变现有代码,而是在中间加一层转换。我个人强烈推荐对象适配器,灵活、安全、符合设计原则。
你想想看,实际项目中我们经常要面对「改不了」的代码——老系统、第三方库、同事写的烂代码。适配器模式给了我们一个优雅的解决方案:不改它,包一层。这就是适配器模式的精髓。
最后提醒一句:适配器模式解决的是接口不兼容问题,不是设计不合理问题。如果接口本身设计就有问题,别硬用适配器,该重构就重构。
公众号:蓝海资料掘金营,微信deep3321