说实话,设计模式这话题被写烂了,网上铺天盖地都是“策略模式”“观察者模式”的教程,但大多数人看的时候热血沸腾,看完合上电脑,遇到真实项目该不会用还是不会用。适配器模式尤其如此——它太“简单”了,简单到很多人觉得不就是写个转换类吗,有什么好学的?但恰恰是这种轻视,导致在实际系统里到处出现硬编码类型转换、接口互相咬合不上、一改需求就崩一片的惨状。我这篇不打算复读教科书,而是从一次真实对接经历讲起,把这个模式从定义、结构、代码到源码里的应用、再到容易混淆的边界,一层层掰开揉碎,最后给出可以直接落地的实操守则。如果你正在做系统集成、老代码改造、SDK封装,或者准备面试被问到“适配器模式怎么用”,这篇应该能帮到你。
1. 从一次真实对接说起:适配器模式解决什么痛点
1.1 新老代码不匹配的尴尬场景
先讲个我自己的经历。前几年接手一个老项目,内部有一套已经跑了七八年的日志系统,对外暴露的是Logger接口,方法名起得随性,什么logInfo、logError,参数还带个int level。后来公司统一技术栈,要求所有服务接入新的日志规范,新规范用的是Slf4j风格的Logger,接口方法变成了info(String msg)、error(String msg, Throwable t)。这下麻烦来了:老系统几十个类全都在调用旧接口,不可能让业务代码直接依赖新日志门面,那样改动量巨大,而且老代码里有些自定义的日志级别逻辑新门面根本不知道。
最粗暴的改法是什么?全局搜索替换,把老接口的调用全部改成新接口。但如果老接口和新接口方法签名不一样、参数类型不一样、异常处理逻辑不一样,替换完就是一遍遍编译报错,报一个改一个,改完还要担心改出运行时问题。我当时在代码评审会上看到有人真提了这样的方案,差点没背过气去。
1.2 适配器模式的核心:不修改原有类,让它适配新接口
这个场景的本质,是两套接口不兼容,而你又不能去改动其中任何一方的源码——老代码是存量资产,改不起;新规范是外部标准,动不了。适配器模式的解法非常朴素:在两者之间插入一个“翻译”,让老实现看起来就像新实现一样,或者反过来,让新调用方通过它能调用老实现。
用生活里的例子类比,你从国外带回来一个两脚插头的电器,家里墙上只有三孔插座,你会去砸墙重铺电路吗?不会。你只需要花几块钱买一个转换插头,一端插电器,另一端插插座,两头都识别不了你是“转换插头”,但电就是这么通了。适配器模式就是软件世界里的转换插头,它负责把某个类的接口转换成客户端期望的另一个接口。这个“协调者”屏蔽了两边的差异,让原本因为接口不匹配而无法协作的类能够一起工作。
1.3 为什么这个模式值得单独研究一遍
可能你会问,Java里的InputStreamReader不也是适配器吗?查一查API文档就完事了,哪里还需要专门写一篇来分析?这恰恰是我写这篇的原因。适配器模式在JDK里有、在Android源码里有、在Spring里有、在任何一个活得够久的系统里都有,但大家对它的理解大多停留在“见过、眼熟、能用”,真正问你“这个模式有几种实现方式”“类适配器和对象适配器分别适用什么场景”“适配器和外观模式有什么本质区别”,不少人又答不上来。这篇就是要解决这些“以为会其实没会”的问题,从角色拆到代码,从源码拆到实战边界,让下一次你遭遇接口不兼容的时候,第一反应不再是一股脑改老代码,而是先想想能不能加一个适配器。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 四种角色与两种实现方向:先看结构再谈代码
2.1 角色拆解:Target、Adaptee、Adapter、Client
适配器模式的静态结构非常清晰,一共四个角色,我建议你先把这个模型印在脑子里,后面所有代码、所有源码分析都逃不出这个框架。
- Target(目标接口):客户端所期望的接口,也就是业务方其实想调用的那个“标准”。在日志场景里,新日志门面就是Target,业务代码希望对着它编程。
- Adaptee(被适配者):已经存在的、接口不兼容但功能正确的类,它就是那个“两脚插头的电器”。老日志系统的
Logger接口及其实现就是Adaptee。 - Adapter(适配器):核心角色,负责把Adaptee的接口转换成Target接口。客户端调用Target接口方法时,Adapter内部将这些调用转发给Adaptee的对应方法,必要时做参数转换、返回值包装、异常翻译。
- Client(客户端):通过Target接口与Adapter交互,完全不感知Adaptee的存在。
画一下依赖关系的话,就是这么个走向:Client依赖Target,Adapter实现Target,同时Adapter持有或继承Adaptee。注意这里面没有一个是多余的——Client不用知道Adaptee的细节,Adaptee也不必为满足Target做任何修改,Adapter是唯一一个需要感知双方的桥梁,所以理论上所有复杂度都被收拢到Adapter内部。
2.2 类适配器与对象适配器:Java与C++的玩法差异
适配器模式有两种经典实现,一种叫类适配器,另一种叫对象适配器。它们的区别从名字就能猜个大概:类适配器用继承(或接口实现)来“蹭”被适配者的能力,对象适配器用组合来持有被适配者的引用。
类适配器的结构是让Adapter同时继承Adaptee并实现Target接口(在Java里由于不能多继承,只能做接口实现,所以这里的“继承”实际是“继承一个类+实现一个接口”)。这样做的好处是可以直接调用Adaptee的protected方法,甚至覆盖它的行为,代码写起来直截了当,不需要额外维护一个内部对象。缺点是继承了Adaptee之后,Adapter和Adaptee就此绑定死了,如果Adaptee不是接口而是具体类,那适配器的复用性就受限了;而且继承会把Adaptee的私有状态也带进来,容易出现莫名其妙的状态干扰。
对象适配器则是Adapter持有Adaptee的引用,在实现Target接口的方法内部去调用Adaptee实例的方法。这种方式在Java、C++、Python里都更常用,因为组合优于继承是软件工程里的老生常谈。它的优点很明显:Adapter不依赖Adaptee的具体子类,任何Adaptee的子类或者代理对象都能传进来,耦合度更低,而且能适配Adaptee的整个子类体系。缺点是要多写一
