← 返回题目列表

装饰器模式有哪些角色?调用流程是什么?

高频 简单 第 2 / 25 题 更新于 2026/07/28
装饰器模式ComponentDecorator调用链

简化版

装饰器模式通常包含抽象组件、具体组件、抽象装饰器和具体装饰器四类角色。调用时客户端面向抽象组件,实际请求会一层层经过装饰器,最后到达具体组件,再把结果逐层返回并增强。

详细版

装饰器模式的四个核心角色是:

  • Component:抽象组件,定义统一接口;
  • ConcreteComponent:具体组件,提供原始功能;
  • Decorator:抽象装饰器,实现同一个接口,并持有 Component
  • ConcreteDecorator:具体装饰器,在调用前后增加功能。

调用链示意:

Client

DecoratorB

DecoratorA

ConcreteComponent

返回时结果再反向经过每层装饰器:

ConcreteComponent 返回结果

DecoratorA 增强结果

DecoratorB 再增强结果

Client 拿到最终结果

示例:

Component component = new ConcreteDecoratorB(
        new ConcreteDecoratorA(
                new ConcreteComponent()
        )
);

component.operation();

每个装饰器只依赖 Component 接口,因此既可以包装真实组件,也可以包装另一个装饰器。这是装饰链能够成立的关键。

完整版教学

一、抽象组件 Component

Component 是装饰器模式的统一入口。

interface DataSource {
    String read();
    void write(String data);
}

客户端不关心拿到的是原始数据源,还是被压缩、加密、缓存包装过的数据源。只要它实现 DataSource,就可以使用。

这个接口非常关键。如果装饰器和真实对象没有共同接口,就不能透明替换。

二、具体组件 ConcreteComponent

具体组件提供基础能力。

class FileDataSource implements DataSource {
    public String read() {
        return "file data";
    }

    public void write(String data) {
        System.out.println("write file: " + data);
    }
}

它只做核心业务,不关心加密、压缩、日志等附加功能。

这样设计的好处是职责清楚。文件数据源负责读写文件;装饰器负责额外能力。

三、抽象装饰器 Decorator

抽象装饰器一般也实现 Component,并持有一个 Component

abstract class DataSourceDecorator implements DataSource {
    protected final DataSource delegate;

    protected DataSourceDecorator(DataSource delegate) {
        this.delegate = delegate;
    }

    public String read() {
        return delegate.read();
    }

    public void write(String data) {
        delegate.write(data);
    }
}

抽象装饰器的作用是减少重复代码。具体装饰器只重写自己要增强的方法。

四、具体装饰器 ConcreteDecorator

具体装饰器负责真正增强。

class EncryptionDecorator extends DataSourceDecorator {
    EncryptionDecorator(DataSource delegate) {
        super(delegate);
    }

    public String read() {
        String data = delegate.read();
        return decrypt(data);
    }

    public void write(String data) {
        delegate.write(encrypt(data));
    }
}

如果再有压缩功能,可以新增:

class CompressionDecorator extends DataSourceDecorator { ... }

然后自由组合:

DataSource source = new CompressionDecorator(
        new EncryptionDecorator(
                new FileDataSource()
        )
);

五、调用流程为什么是一层层的

因为每个装饰器都持有下一个 Component

执行:

source.write("hello");

如果 sourceCompressionDecorator,调用先进入压缩装饰器;压缩后调用 EncryptionDecorator;加密后再调用 FileDataSource

这就是装饰链。

顺序非常重要。压缩后加密,和加密后压缩,结果可能完全不同。面试时如果能主动提到装饰顺序,答案会更有工程味。

六、四类角色如何闭环

基础咖啡 10 元,经牛奶装饰加 3 元、糖装饰加 2 元,最外层 cost() 递归得到 15 元,每层只贡献自己的增量。

Client -> ConcreteDecorator -> Decorator.component -> ConcreteComponent

这个推演把模式带来的收益和成本放在同一条调用链上。设计评审时既要确认结果正确,也要确认额外层次没有改变原有契约。

七、边界、代价与验证

检查维度应确认的内容
正确性角色名可以变化,判断模式要追踪接口契约和委托链,不能机械数类。
适用边界抽象 Decorator 可复用委托代码,但不是语法强制;关键是共同接口、对象组合和可递归包装。
测试证据覆盖正常路径、异常路径、空值或未知类型,并断言调用次数与顺序
运行成本记录额外对象、调用层数、延迟、线程或内存开销,不用模式名称代替测量

模式落地后要用单元测试验证独立职责,用集成测试验证对象装配和真实调用入口。涉及并发、远程或异步时,还要补充竞态、超时、重复执行与资源释放测试。

易错点:角色名可以变化,判断模式要追踪接口契约和委托链,不能机械数类。

八、常见误区与追问

  • 误区:没有抽象 Decorator 类就不算装饰器模式。 可以直接让具体装饰器实现 Component 并持有 Component,抽象基类只是减少重复。
  • 误区:装饰器可以任意改变组件接口。 透明装饰依赖共同的 Component 契约;随意增加只能向下转型调用的方法,会破坏可替换性和继续组合的能力。
  • 误区:示例代码能运行,就代表模式边界设计正确。 能运行只证明一条路径,仍要检查异常、并发、顺序、生命周期以及新增实现时是否修改稳定代码。
  • 追问:最外层对象为什么仍能当作 Component 使用? 每一层都实现同一接口,满足可替换性,因此客户端无需知道内部层数。
  • 追问:装饰器链越长,扩展性就一定越好吗? 不一定;链越长,顺序、异常传播和排障成本越高,应由装配层集中管理并限制职责粒度。
  • 追问:怎样验证这套设计没有改变原有语义? 先做基线行为测试,再对新增层验证返回值、异常类型、调用次数和顺序;性能敏感处还要比较改造前后的量化指标。

九、加强记忆

装饰器模式的角色可以记成:接口定规矩,具体组件做基础能力,抽象装饰器保存被包装对象,具体装饰器负责加一层能力。调用是一层层进去,再一层层返回。