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

代理模式 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:适配器(接口不匹配,转换)
java 复制代码
// 目标接口:系统统一支付标准
public interface Payment {
    void pay(BigDecimal money);
}

// 第三方老支付SDK(接口不兼容)
public class OldThirdPay {
    public void transfer(BigDecimal cash, Integer type) {
        System.out.println("第三方转账:"+cash);
    }
}

// 适配器:统一转换成Payment接口
public class PayAdapter implements Payment {
    private OldThirdPay adaptee = new OldThirdPay();

    @Override
    public void pay(BigDecimal money) {
        // 转换参数,适配第三方接口
        adaptee.transfer(money, 1);
    }
}
// 使用:业务只认Payment,完全不知道OldThirdPay存在
Payment pay = new PayAdapter();
pay.pay(new BigDecimal(100));
示例2:代理(接口完全一致,增加缓存增强)
java 复制代码
// 统一抽象接口
public interface OrderService {
    Order getOrder(Long id);
}

// 真实业务类
public class OrderServiceImpl implements OrderService {
    @Override
    public Order getOrder(Long id) {
        // 查询数据库
        return db.selectById(id);
    }
}

// 缓存代理,接口完全一致
public class OrderProxy implements OrderService {
    private OrderService real = new OrderServiceImpl();
    private Map<Long, Order> cache = new HashMap<>();

    @Override
    public Order getOrder(Long id) {
        // 前置增强:缓存拦截
        if (cache.containsKey(id)) {
            return cache.get(id);
        }
        Order order = real.getOrder(id);
        // 后置增强:放入缓存
        cache.put(id, order);
        return order;
    }
}
// 使用方式和原对象完全一致,上层无感知
OrderService service = new OrderProxy();
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

六、一句话记忆口诀

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

相关推荐
AI人工智能+电脑小能手4 天前
【大白话说Java面试题 第156题】【06_Spring篇】第16题:Spring 用到了哪些设计模式?
java·spring·设计模式·代理模式·工厂模式
许彰午25 天前
36_Java设计模式之代理模式
java·设计模式·代理模式
折哥的程序人生 · 物流技术专研1 个月前
Java 23 种设计模式:从踩坑到精通 | 适配器模式 —— 让不兼容的接口也能一起工作
java·设计模式·面试·适配器模式·单一职责原则
devilnumber1 个月前
静态代理 & 动态代理:实战运用 + 场景区别 + 怎么选
java·开发语言·代理模式
devilnumber1 个月前
想真正吃透 + 灵活运用 Java 代理模式
java·开发语言·代理模式
故渊at1 个月前
系列一:架构思想进阶 | 第3篇 SOLID 原则与设计模式实战:从“代码搬运工”到“架构师”的必经之路
观察者模式·设计模式·重构·架构·代理模式
basketball6161 个月前
设计模式入门:5. 代理模式详解 C++实现
c++·设计模式·代理模式
2601_961194021 个月前
2026初级会计经济法基础知识点汇总
python·django·pdf·virtualenv·代理模式·pygame
2601_961194021 个月前
考研模拟卷谁的比较好|27李林合工大肖四肖八数学英语408PDF
考研·elasticsearch·全文检索·代理模式·lucene·桥接模式·访问者模式