静态代理 (Proxy)、装饰器 (Decorator)、适配器 (Adapter)、桥接 (Bridge)详解

静态代理 (Proxy)装饰器 (Decorator)适配器 (Adapter)桥接 (Bridge)详解

总结

他们都是结构性模式

终极四合一对比表(一网打尽)

模式 引用关系 核心目的 一句话人话
代理 (Proxy) 引用同类型对象 控制访问(拦截、延迟、权限) “过安检,决定让不让你进”
装饰器 (Decorator) 引用同类型对象 增强功能(叠加额外行为) “穿装备,一层层变强”
适配器 (Adapter) 引用不同类型对象 接口转换(翻译不兼容的接口) “带转接头,让美标插头插进国标插座”
桥接 (Bridge) 引用不同类型对象 解耦抽象与实现(双维度独立变化) “遥控器(抽象)和电视品牌(实现)分开发展”

辨别

当你看到一段代码,内部持有一个引用时:

  1. 先看接口:和自己是同一个接口吗?
    • → 再看意图:是拦住不让进(代理)?还是加了点料再放行(装饰器)?
    • → 再看意图:是为了翻译不兼容的接口(适配器)?还是为了把“做什么”和“怎么做”拆开(桥接)?
  2. 记住两个死对头
    • 代理 vs 装饰器:结构一样(都同类型),区别在意图(控制 vs 增强)。
    • 适配器 vs 桥接:结构一样(都不同类型),区别在时机和维度(事后补救 vs 事前设计;单接口转换 vs 双维度扩展)。

面试加分金句(直接背)

代理和装饰器不改变接口,但代理管‘能不能用’,装饰器管‘好不好用’;适配器和桥接都涉及接口不兼容,但适配器是‘打补丁’,桥接是‘搭架构’。

代理(Proxy)

引用的是同类型的对象(实现同一接口),目的是控制访问。

  • 引用关系:代理和目标对象实现同一个接口(或继承同一个抽象类),代理内部持有该接口的引用。
  • 核心目的控制对目标对象的访问(如权限校验、延迟加载、日志记录、远程代理),不改变对象行为,只决定“让不让用、什么时候用”。
  • 控制性:代理是“守门员”,可以拦截请求、修改请求参数、甚至拒绝访问,但最终目标对象的逻辑不变。
  • 典型场景:虚拟代理(大图占位)、保护代理(权限检查)、远程代理(RPC)、Spring AOP 动态代理。
// 代理和目标实现同一接口
interface Image { void display(); }

class HighResImage implements Image {
    public HighResImage(String filename) {
        loadFromDisk();  // 耗时操作
    }
    public void display() { /* 显示图片 */ }
}

class ProxyImage implements Image {
    private String filename;
    private HighResImage realImage;  // 同类型引用

    public void display() {
        if (realImage == null) {
            realImage = new HighResImage(filename);  // 延迟加载(控制访问时机)
        }
        realImage.display();  // 委托给原对象
    }
}
// 客户端:new ProxyImage("photo.jpg"),访问被代理控制,图片直到真正需要时才加载

装饰器(Decorator)

引用的是同类型的对象(实现同一接口),目的是增强功能。

  • 引用关系:装饰器和被装饰对象实现同一个接口(或继承同一个抽象类),装饰器内部持有该接口的引用。
  • 核心目的动态叠加额外职责(如加缓存、加加密、加缓冲),不改变对象本质,只是“穿马甲”增强。
  • 嵌套性:可以无限层层包装,每一层都增强一点。
  • 典型场景:Java I/O 流(new BufferedInputStream(new FileInputStream(...)))、Spring 缓存装饰。
// 装饰器和目标实现同一接口
interface DataSource { void write(String data); }

class FileDataSource implements DataSource { ... }  // 原始对象

class EncryptionDecorator implements DataSource {
    private DataSource wrapped;  // 同类型引用
    public void write(String data) {
        String encrypted = encrypt(data);  // 增强功能-前置增强
        wrapped.write(encrypted);          // 委托给原对象
        System.out.println("写入完成:" + data); // 可以增加其他逻辑-后置增强
    }
}
// 客户端:new EncryptionDecorator(new FileDataSource()),功能增强但不改接口

适配器(Adapter)

引用的是不同类型的对象(接口不兼容),目的是接口转换。

  • 引用关系:适配器实现客户端期望的目标接口,内部持有被适配者(Adaptee)的引用,两者接口不同。
  • 核心目的让两个不兼容的接口能一起工作,做的是“翻译”或“转接头”的工作。
  • 事后性:通常是事后补救(两个已存在的模块不兼容,强行搭桥),而不是事先设计。
  • 典型场景:电源转接头、不同日志框架统一为 SLF4J、Arrays.asList() 将数组适配为 List。
// 适配器实现目标接口,但持有不同类型的被适配者引用
interface Target { void request(); }          // 客户端期望的接口

class Adaptee { void specificRequest() { } }  // 已有的不兼容接口

class Adapter implements Target {
    private Adaptee adaptee;  // 不同类型引用(不是Target类型)
    public void request() {
        adaptee.specificRequest();  // 翻译调用,接口转换
    }
}
// 客户端:new Adapter(new Adaptee()),用Target接口的request方法调用了Adaptee的方法

桥接(Bridge)

引用的是不同类型的对象(实现接口与抽象接口不同),目的是解耦抽象与实现。

  • 引用关系:抽象类持有实现接口的引用,两者是不同类型的接口体系(抽象接口 ≠ 实现接口)。
  • 核心目的将抽象部分与实现部分分离,让两者可以独立变化,避免因多维度变化导致类爆炸(M×N → M+N)。
  • 事前性:是事先设计的结构型模式,用于应对未来多个维度的扩展,而不是事后打补丁。
  • 典型场景:JDBC 驱动(DriverManager 抽象 + 各数据库实现)、不同形状(圆/方)+ 不同颜色(红/蓝)、不同操作系统 + 不同文件系统。
// 抽象和实现是两个独立的接口体系
interface Color { void applyColor(); }          // 实现维度

class Red implements Color {
    public void applyColor() { System.out.println("红色"); }
}

class Blue implements Color {
    public void applyColor() { System.out.println("蓝色"); }
}

abstract class Shape {                          // 抽象维度
    protected Color color;  // 不同类型引用(Color 不是 Shape)
    public Shape(Color color) { this.color = color; }
    abstract void draw();
}

class Circle extends Shape {
    public Circle(Color color) { super(color); }
    public void draw() {
        color.applyColor();  // 委托给实现,形状和颜色独立变化
        System.out.println("画圆形");
    }
}

class Square extends Shape {
    public Square(Color color) { super(color); }
    public void draw() {
        color.applyColor();  // 委托给实现
        System.out.println("画方形");
    }
}
// 客户端:new Circle(new Red()),形状和颜色两个维度可以任意组合,无需新增类

桥接和适配器对照表

模式 代码特征 核心判断 一句话
桥接 a.method1() 内部调用 b.method2()method1 和 method2 语义一致,【分工合作:method2是method1需要完成功能的一部分】 两个接口天生匹配,无需翻译,只是把任务委托出去(我要画圆draw(),你帮我上色applyColor(),都是“画”这个大的语义范畴) “我要用你的能力,直接调用”
适配器 a.method1() 内部调用 b.method2()method1 和 method2 语义一致,【带转接头:method2和method1功能一致,但是方法名不一样,所以翻译一下】 两个接口本来不匹配,适配器做翻译,把 method1 转成 method2 “我要用你的能力,但接口对不上,得翻译一下”

桥接a.method1() 内部调用 b.method2(),语义一致。

  • 【分工合作】method2method1 需要完成功能的一小部分(整体与局部的关系)。
  • 桥接method2method1 为了完成职责,而拆解出来的内部协作部分

适配器a.method1() 内部调用 b.method2(),语义一致。

  • 【带转接头】method2method1 功能完全等价,但方法名/参数不对,所以翻译一下(完全对等的关系)。
  • 适配器method2method1 为了兼容外部系统,而引用的外部对等功能
模式 核心比喻 精确的职责描述
桥接 分工合作(整体 vs 局部) method2method1 不可分割的内部实现细节(例如:画圆必须用到上色)。
适配器 带转接头(完全对等) method2 是来自外部第三方、与 method1 完全对等的功能(例如:我要供电,你也能供电,但你是美标)。

适配器和桥接在代码层面可以完全一样,区别只在于"设计意图"和"上下文"。

证明:同一套代码,两种解释

通用代码结构

interface Target {
    void doSomething();
}

class Adaptee {
    void specificAction() { }
}

class SomeClass implements Target {
    private Adaptee adaptee;
    
    public SomeClass(Adaptee adaptee) {
        this.adaptee = adaptee;
    }
    
    @Override
    public void doSomething() {
        adaptee.specificAction();
    }
}

问题: 这段代码是适配器还是桥接?


答案:取决于你怎么说
解释角度 说它是适配器的理由 说它是桥接的理由
接口关系 TargetAdaptee 是两个不相关的系统,接口不兼容,需要翻译 Target 是抽象层,Adaptee 是实现层,两者本来就是设计好的两个维度
设计时机 事后补救:两个已存在的模块不兼容,强行搭桥 事前设计:架构师在设计时就把"做什么"和"怎么做"分开了
语义关系 specificAction()doSomething() 是等价的(都是完成同一个业务目标) specificAction()doSomething() 的一部分(分工合作)
扩展方向 单维度:适配器只是让一个外部类接入目标接口 双维度:TargetAdaptee 各自可以有很多子类,独立扩展

经典案例:JDBC 是适配器还是桥接?
// Java 代码
Connection conn = DriverManager.getConnection(url);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");

官方说法: JDBC 是桥接模式(抽象是 JDBC API,实现是各数据库驱动)

但也可以说是适配器: 因为 MySQL、Oracle、PostgreSQL 的驱动接口完全不同,JDBC 把它们都适配成统一的 ConnectionStatement 接口

真相: JDBC 两者都是

  • 从 Sun 公司设计 JDBC 的初衷看 → 桥接(抽象 API 和具体驱动独立发展)
  • 从各数据库厂商接入 JDBC 的过程看 → 适配器(把自己的接口翻译成 JDBC 接口)

所以 JDBC 官方文档从不纠结它是什么模式,只说"它是一个分层架构"。


为什么设计模式会有这种"模糊地带"?
1. 模式是"意图",不是"公式"

设计模式不是数学公式,不是"代码长这样 = 模式 X"。它们是沟通工具,用来表达你的设计意图

// 同一段代码
class X implements A {
    private B b;
    public void method() {
        b.anotherMethod();
    }
}
  • 如果你说:"我为了解耦抽象和实现" → 桥接
  • 如果你说:"我为了兼容第三方的 B 类" → 适配器

代码没变,变的是你的解释。


2. 设计模式可以"演化"

很多设计模式在实际项目中会相互转化:

项目初期:适配器(快速接入第三方)
    ↓ 演化
项目中期:桥接(抽象和实现独立扩展)
    ↓ 演化
项目后期:策略模式(实现变成可插拔的算法)

同一个类,在不同阶段可以有不同的模式解读。

某段代码关于它是代理还是装饰器的理解

问题:这个我感觉可以理解成代理也可以理解成装饰器吧,我更倾向装饰器

//抽象角色:租房
public interface Rent {
   public void rent();
}
//真实角色: 房东,房东要出租房子
public class Host implements Rent{
   public void rent() {
       System.out.println("房屋出租");
  }
}
//代理角色:中介
public class Proxy implements Rent {

   private Host host;
   public Proxy() { }
   public Proxy(Host host) {
       this.host = host;
  }

   //租房
   public void rent(){
       seeHouse();
       host.rent();
       fare();
  }
   //看房
   public void seeHouse(){
       System.out.println("带房客看房");
  }
   //收中介费
   public void fare(){
       System.out.println("收中介费");
  }
}

解答

这段代码既是代理也是装饰器,区别在于你怎么解释它。从业务角色看,中介是房东的代理,控制租客对房东的访问,所以是代理模式;从代码结构看,它持有同类型引用,在租房的逻辑前后增加了额外行为,所以也符合装饰器模式。设计模式的核心是'意图',不是'代码形状'。在实际开发中,一个类同时具备多种模式的特征非常常见。

结论先行

视角 判断依据 结论
从"控制访问"看 中介控制了"租客能否见到房东"的入口 代理
从"增强功能"看 看房 + 收中介费 是"租房"行为的增强 装饰器
从"真实业务"看 现实中,中介就是"代理"(委托代理关系) 代理更贴切
从"代码结构"看 和装饰器完全一样,都是持有同类型引用并增强 两者都可

最精准的说法: 这是代理模式的经典案例,但它的代码结构和装饰器完全相同,区别只在"你怎么解释它"。


1. 为什么说它是代理?(业务语义)

public class Proxy implements Rent {
    private Host host;
    
    public void rent() {
        seeHouse();    // 前置增强:看房
        host.rent();   // 委托给真正的房东
        fare();        // 后置增强:收中介费
    }
}

代理的理由:

  • 控制访问:租客不能直接联系房东,必须通过中介
  • 延迟/筛选:中介可以决定"不让某个租客看房"(控制访问权限)
  • 现实映射:房地产中介就是典型的"代理"角色

这就是教科书上的"静态代理"案例。


2. 为什么也可以说是装饰器?(代码结构)

interface Rent { void rent(); }
class Host implements Rent { ... }
class Proxy implements Rent {
    private Rent wrapped;  // 同类型引用
    public void rent() {
        // 增强逻辑
        wrapped.rent();    // 委托
        // 增强逻辑
    }
}

装饰器的理由:

  • ✅ 实现了同一接口(Rent
  • ✅ 持有同类型引用(Rent wrapped
  • ✅ 在调用前后增加了额外功能(看房 + 收中介费)
  • ✅ 不改变 Host.rent() 的本质行为

从纯代码角度,它和装饰器一模一样。


3. 为什么你"更倾向装饰器"是对的?

你的直觉非常准!因为这段代码的增强性非常明显:

增强点 说明
seeHouse() 前置增强:租房前先看房
host.rent() 委托给原对象,行为不变
fare() 后置增强:租房后收中介费

如果去掉"中介"这个业务背景,只看代码:

  • 它就是一个 "增强的租房服务"
  • BufferedInputStream 装饰 FileInputStream 没有任何区别

所以你"更倾向装饰器"完全有道理!


4. 那到底算哪个?—— 取决于上下文

场景 应该叫它什么 理由
你在讲"静态代理"的教程 代理 这是代理模式的经典范例
你在讲"增强租房功能" 装饰器 它确实在增强 Host.rent()
你在设计模式考试 代理 因为"中介"的角色就是代理
你在重构代码 装饰器 如果后续要嵌套多个增强(如"免中介费装饰器")
你在面试 都可以 重点说清楚"为什么"(意图决定模式)

5. 如果你要改成"纯装饰器"风格

// 装饰器风格:把 Host 传进去
class RentDecorator implements Rent {
    private Rent wrapped;  // 更通用的命名
    
    public RentDecorator(Rent wrapped) {
        this.wrapped = wrapped;
    }
    
    @Override
    public void rent() {
        seeHouse();
        wrapped.rent();
        fare();
    }
}

// 客户端:像装饰器一样嵌套
Rent rent = new RentDecorator(new Host());
rent.rent();

这样写之后,它更"像"装饰器了,但业务上它还是代理。


6. 最终判断法则(看意图)

判断维度 代理 装饰器
核心意图 控制访问 增强功能
是否拦截 是(可以拒绝访问) 否(不拒绝,只增强)
是否嵌套 一般不嵌套 可以无限嵌套
创建目标对象 可以延迟创建 创建时必须传入
客户端感知 通常无感知 客户端知道在装饰

在这段代码里:

  • 中介可以"拒绝带看"(控制访问) → 代理特征
  • 中介的增强是一层层的 → 装饰器特征
  • 创建时就传入了 Host装饰器特征
posted @ 2026-06-23 12:14  deyang  阅读(10)  评论(0)    收藏  举报