代理模式和适配器模式区别

代理模式 vs 适配器模式 完整详解+核心区别

一、先分别吃透两种模式本质

1. 适配器模式(Adapter,结构型)

核心目的:做「转换兼容」
两个已有接口不匹配,通过适配器做一层接口转换,让原本不能协作的两类代码一起工作。

  • 关键词:兼容、翻译、转换、统一接口
  • 角色:
    • 目标接口 Target:我方程序期望调用的统一接口
    • 被适配者 Adaptee:已有旧接口/第三方类,方法名、参数和我方不匹配
    • 适配器 Adapter:包装 Adaptee,实现 Target,内部做参数、方法转发转换
  • 类比:转换插头
    插座是标准Target(国标三脚),国外电器是Adaptee(美标两脚),适配器插头做转换,让电器插上插座。
两种实现
  1. 类适配器:继承被适配类 + 实现目标接口(Java单继承很少用)
  2. 对象适配器:组合持有被适配对象(主流)

2. 代理模式(Proxy,结构型)

核心目的:做「控制增强」
用一个代理类包装原始目标对象,对外暴露完全一模一样的接口,不修改原有接口定义,在方法前后增加额外逻辑(权限、缓存、日志、延时创建、远程调用)。

  • 关键词:控制、增强、拦截、中介、透明访问
  • 角色:
    • 抽象主题 Subject:原始对象与代理共同实现的统一接口
    • 真实对象 RealSubject:业务核心对象
    • 代理 Proxy:实现同一接口,持有真实对象,拦截所有方法
  • 类比:中介/经纪人
    明星(RealSubject)和经纪人(Proxy)对外联系方式完全一致(都能接演出),但经纪人会做前置筛选报价、后置签合同,明星只负责表演。
常见分类
  • 静态代理:手写代理类
  • 动态代理:JDK动态代理、CGLIB(Spring AOP底层就是动态代理)
  • 远程代理、虚拟代理、保护代理、缓存代理

二、最核心本质差异(一句话区分)

  1. 适配器:接口不一样,做转换,解决「不兼容」
    对外提供新接口,抹平新旧/第三方接口差异;使用者看不到原始类接口。
  2. 代理:接口完全一样,做增强,控制访问
    对外和原始对象完全相同接口,使用者无感知,透明调用,只附加额外逻辑。

关键分水岭:接口是否改变

  • 调用方期望的接口 ≠ 原始类接口 → 适配器
  • 调用方期望的接口 = 原始类接口 → 代理

三、分层详细对比

1. 设计目标不同

适配器

打通两套不兼容接口,复用已有第三方/旧代码,不用修改原有代码。
场景:老系统接口、第三方SDK、不同组件整合。

代理

在不改动原对象代码前提下,对原有功能进行拦截、增强、管控
场景:权限校验、日志、缓存、懒加载、远程RPC、事务、限流。

2. 对外暴露接口

适配器

适配器实现全新的目标接口Target,内部调用被适配者Adaptee的异构方法。
外部调用Adapter的是一套新方法名,和Adaptee方法名完全不同。
示例:

  • Target:pay(amount)
  • Adaptee第三方支付:transferMoney(money, channel)
  • Adapter内部:pay()里转换参数调用transferMoney()
代理

代理和真实对象实现同一个接口Subject,方法名、参数、返回值完全一致。
外部调用Proxy的方法,和直接调用RealSubject无任何区别。
示例:

  • 统一接口:queryOrder(Long id)
  • Proxy.queryOrder() 先查缓存,无缓存再调用 RealSubject.queryOrder()

3. 与被包装对象的关系

适配器

被包装的 Adaptee 是外部/老旧异构组件,本来就不适合当前系统调用规范,两者设计初衷无关。
适配器是翻译官

代理

被包装的 RealSubject 是系统核心业务对象,本身完全满足业务功能,只是需要附加控制逻辑。
代理是保镖/中介

4. 是否改变调用行为

适配器

核心逻辑委托给被适配者,适配器只做参数、格式转换,不新增额外横切逻辑。
没有前置校验、缓存、日志这类增强,仅做格式翻译。

代理

核心逻辑依然委托真实对象,但会增加前后附加逻辑
前置:权限校验、参数校验、缓存查询
后置:日志记录、耗时统计、事务提交

5. 代码示例直观区分

示例1:适配器(接口不匹配,转换)
// 目标接口:系统统一支付标准publicinterfacePayment{voidpay(BigDecimalmoney);}// 第三方老支付SDK(接口不兼容)publicclassOldThirdPay{publicvoidtransfer(BigDecimalcash,Integertype){System.out.println("第三方转账:"+cash);}}// 适配器:统一转换成Payment接口publicclassPayAdapterimplementsPayment{privateOldThirdPayadaptee=newOldThirdPay();@Overridepublicvoidpay(BigDecimalmoney){// 转换参数,适配第三方接口adaptee.transfer(money,1);}}// 使用:业务只认Payment,完全不知道OldThirdPay存在Paymentpay=newPayAdapter();pay.pay(newBigDecimal(100));
示例2:代理(接口完全一致,增加缓存增强)
// 统一抽象接口publicinterfaceOrderService{OrdergetOrder(Longid);}// 真实业务类publicclassOrderServiceImplimplementsOrderService{@OverridepublicOrdergetOrder(Longid){// 查询数据库returndb.selectById(id);}}// 缓存代理,接口完全一致publicclassOrderProxyimplementsOrderService{privateOrderServicereal=newOrderServiceImpl();privateMap<Long,Order>cache=newHashMap<>();@OverridepublicOrdergetOrder(Longid){// 前置增强:缓存拦截if(cache.containsKey(id)){returncache.get(id);}Orderorder=real.getOrder(id);// 后置增强:放入缓存cache.put(id,order);returnorder;}}// 使用方式和原对象完全一致,上层无感知OrderServiceservice=newOrderProxy();service.getOrder(1001L);

6. 适用业务场景对比

适配器典型场景
  1. 整合第三方SDK(微信/支付宝老版本接口、OSS存储、第三方短信)
  2. 新旧系统兼容:老数据库DAO、遗留接口适配新业务框架
  3. 统一多类异构工具:多种日志组件、多种文件存储适配统一工具类
  4. 不同协议转换:HTTP接口适配内部RPC调用格式
代理典型场景
  1. 动态代理做AOP:日志、切面、事务、限流、权限校验(Spring核心)
  2. 缓存代理:数据库查询加本地缓存、Redis缓存
  3. 虚拟代理:大图片/大文件延迟加载(用到才创建真实对象)
  4. 保护代理:接口访问权限校验,未登录拦截调用
  5. 远程代理:RPC客户端代理,屏蔽网络通信细节(Dubbo/Feign)
  6. 静态代理:封装工具类增加统一异常处理

7. 优缺点与取舍

适配器优点

快速兼容异构代码,无需修改第三方/遗留类;统一对外调用标准。
缺点:仅做转换,无法统一增加横切增强逻辑。

代理优点

无侵入增强原有业务,上层调用零改动;动态代理可统一批量切面处理。
缺点:只能用于接口匹配的场景,不能解决接口不兼容问题。


四、表格汇总对比

对比维度适配器模式(Adapter)代理模式(Proxy)
核心职责接口转换、兼容异构组件透明拦截、功能增强、访问控制
对外接口提供全新目标接口,与被包装对象接口不同和原始对象完全相同接口,方法签名一致
设计关系解决「接口不匹配」问题不改变接口,附加额外逻辑
类比转换插头、翻译官经纪人、中介、保镖
包装对象第三方/遗留异构类系统原生标准业务对象
附加逻辑仅参数/格式转换,无横切增强缓存、日志、权限、事务、延时加载
上层感知上层只依赖Target,看不到原始类上层完全无感知,像直接调用原对象
典型框架应用SpringMVC HandlerAdapter、集合工具适配Spring AOP动态代理、Dubbo远程代理

五、极易混淆的边界场景区分技巧

场景1:如果我包装后,方法名变了 → 适配器

需要把sendMsg(String phone,String content)改成系统统一sendSms(SmsDTO dto),参数结构重组,一定是适配器。

场景2:包装后方法名、参数完全不变,只是加日志/缓存 → 代理

getUser(Long id)前后加打印耗时、缓存查询,接口一丝不动,一定是代理。

场景3:同时需要转换+增强怎么处理?

分层组合:外层代理做增强,内层适配器做转换
调用链路:Controller → Proxy(日志/权限)→ Adapter(接口转换)→ 第三方SDK

六、一句话记忆口诀

接口不一样,要兼容,用适配器;
接口一模一样,要增强,用代理。