Skip to content

装饰器模式 (Decorator Pattern)

一、定义

一句话概括:在不改变原有对象结构的情况下,动态地给对象添加额外功能。

官方定义(GoF):Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

装饰器模式的核心思想是:用组合+委托代替继承来扩展功能。它创建一个装饰类来包装原始类,在保持接口一致的前提下,在方法调用前后添加新行为。


二、解决的问题

2.1 什么场景下需要装饰器模式?

  • 动态扩展功能:需要在运行时给对象添加功能,而不是编译时通过继承固定。
  • 功能组合:需要灵活组合多个功能,如"带缓冲的、带压缩的、带加密的"输出流。
  • 避免类爆炸:用继承实现功能组合会导致子类数量指数级增长。
  • 不修改原代码:原始类已经稳定,不能或不想修改其源码。

2.2 不用装饰器模式会有什么问题?

假设我们要设计一个咖啡订单系统,需要支持加牛奶、加糖、加摩卡等配料。

继承方案:

Coffee
├── MilkCoffee
├── SugarCoffee
├── MochaCoffee
├── MilkSugarCoffee
├── MilkMochaCoffee
├── SugarMochaCoffee
├── MilkSugarMochaCoffee
...(指数级增长)
问题1:类爆炸 —— 3 种配料产生 2^3 = 8 个子类,n 种配料产生 2^n 个子类
问题2:编译时绑定 —— 功能组合在编译时确定,无法运行时动态调整
问题3:违反开闭原则 —— 新增配料需要修改多个类
问题4:强耦合 —— 子类与父类紧耦合,难以独立演化

三、结构

3.1 文字描述

装饰器模式包含以下角色:

  • Component(抽象构件):定义原始对象和装饰器的公共接口。
  • ConcreteComponent(具体构件):被装饰的原始对象。
  • Decorator(抽象装饰器):持有一个 Component 引用,实现 Component 接口。
  • ConcreteDecorator(具体装饰器):在调用被装饰对象的方法前后添加增强逻辑。

3.2 ASCII 类图

┌─────────────────────────┐
│  <<interface/abstract>>  │
│       Component          │
├─────────────────────────┤
│ + operation()            │
└────────────┬────────────┘

    ┌────────┴──────────────────────┐
    │                               │
┌───┴──────────────────┐  ┌────────┴──────────────────┐
│  ConcreteComponent   │  │       Decorator            │
├──────────────────────┤  ├───────────────────────────┤
│ + operation()        │  │ # component: Component     │
│   原始实现            │  │ + Decorator(Component)     │
└──────────────────────┘  │ + operation()              │
                          │   component.operation()    │
                          └────────────┬──────────────┘

                          ┌────────────┴──────────────┐
                          │                           │
                 ┌────────┴──────────┐    ┌───────────┴──────────┐
                 │ ConcreteDecoratorA│    │ ConcreteDecoratorB   │
                 ├───────────────────┤    ├──────────────────────┤
                 │ + operation()     │    │ + operation()        │
                 │   pre-process()   │    │   component.op()     │
                 │   component.op()  │    │   post-process()     │
                 └───────────────────┘    └──────────────────────┘

四、代码实现

4.1 基础实现

场景:咖啡订单系统

java
// ============ Component:抽象构件 ============
interface Beverage {
    String getDescription();
    double cost();
}

// ============ ConcreteComponent:具体构件 ============
class Espresso implements Beverage {
    @Override
    public String getDescription() {
        return "浓缩咖啡";
    }

    @Override
    public double cost() {
        return 15.0;
    }
}

class HouseBlend implements Beverage {
    @Override
    public String getDescription() {
        return "综合咖啡";
    }

    @Override
    public double cost() {
        return 12.0;
    }
}

// ============ Decorator:抽象装饰器 ============
abstract class CondimentDecorator implements Beverage {
    protected Beverage beverage;  // 被装饰的对象

    public CondimentDecorator(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public abstract String getDescription();
}

// ============ ConcreteDecorator:具体装饰器 ============
class Milk extends CondimentDecorator {
    public Milk(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + " + 牛奶";
    }

    @Override
    public double cost() {
        return beverage.cost() + 3.0;
    }
}

class Mocha extends CondimentDecorator {
    public Mocha(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + " + 摩卡";
    }

    @Override
    public double cost() {
        return beverage.cost() + 4.0;
    }
}

class Whip extends CondimentDecorator {
    public Whip(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + " + 奶泡";
    }

    @Override
    public double cost() {
        return beverage.cost() + 2.0;
    }
}

// ============ 客户端测试 ============
public class DecoratorDemo {
    public static void main(String[] args) {
        // 浓缩咖啡 + 牛奶 + 摩卡 + 奶泡
        Beverage order = new Espresso();
        order = new Milk(order);
        order = new Mocha(order);
        order = new Whip(order);

        System.out.println("订单: " + order.getDescription());
        System.out.println("总价: ¥" + order.cost());
        // 输出:订单: 浓缩咖啡 + 牛奶 + 摩卡 + 奶泡
        //       总价: ¥24.0
    }
}

4.2 进阶实现

4.2.1 Java IO 的装饰器体系(重点)

Java IO 是装饰器模式最经典的工业级应用。整个体系分为"节点流"(被装饰者)和"处理流"(装饰器)。

java
// ============ Java IO 装饰器体系结构 ============
//
// InputStream(抽象 Component)
// ├── FileInputStream(ConcreteComponent —— 节点流)
// ├── ByteArrayInputStream(ConcreteComponent —— 节点流)
// ├── FilterInputStream(抽象 Decorator)
// │   ├── BufferedInputStream(ConcreteDecorator —— 加缓冲)
// │   ├── DataInputStream(ConcreteDecorator —— 读基本类型)
// │   ├── PushbackInputStream(ConcreteDecorator —— 可回退)
// │   └── CheckedInputStream(ConcreteDecorator —— 加校验和)
// └── ObjectInputStream(ConcreteDecorator —— 反序列化)

// ============ 手动模拟 Java IO 装饰器 ============

// 抽象 Component
abstract class MyInputStream {
    public abstract int read();
    public abstract void close();
}

// 具体 Component(节点流)
class MyFileInputStream extends MyInputStream {
    private String filePath;

    public MyFileInputStream(String filePath) {
        this.filePath = filePath;
        System.out.println("打开文件: " + filePath);
    }

    @Override
    public int read() {
        System.out.println("从文件读取一个字节");
        return 'A';  // 模拟
    }

    @Override
    public void close() {
        System.out.println("关闭文件: " + filePath);
    }
}

// 抽象 Decorator
abstract class MyFilterInputStream extends MyInputStream {
    protected MyInputStream in;  // 被装饰的流

    public MyFilterInputStream(MyInputStream in) {
        this.in = in;
    }

    @Override
    public int read() {
        return in.read();
    }

    @Override
    public void close() {
        in.close();
    }
}

// 具体 Decorator:缓冲流
class MyBufferedInputStream extends MyFilterInputStream {
    private byte[] buffer = new byte[8192];
    private int pos = 0;
    private int count = 0;

    public MyBufferedInputStream(MyInputStream in) {
        super(in);
    }

    @Override
    public int read() {
        if (pos >= count) {
            fillBuffer();
        }
        if (count == -1) return -1;
        return buffer[pos++] & 0xFF;
    }

    private void fillBuffer() {
        System.out.println("[缓冲] 填充缓冲区...");
        pos = 0;
        count = 0;
        for (int i = 0; i < buffer.length; i++) {
            int b = in.read();
            if (b == -1) { count = i == 0 ? -1 : i; return; }
            buffer[i] = (byte) b;
            count++;
        }
    }
}

// 具体 Decorator:数据流(读基本类型)
class MyDataInputStream extends MyFilterInputStream {
    public MyDataInputStream(MyInputStream in) {
        super(in);
    }

    public byte readByte() {
        return (byte) read();
    }

    public int readInt() {
        int ch1 = read();
        int ch2 = read();
        int ch3 = read();
        int ch4 = read();
        return (ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4;
    }
}

// ============ 测试:多层装饰 ============
public class IODecoratorDemo {
    public static void main(String[] args) {
        // 文件输入 → 缓冲 → 数据读取
        MyInputStream raw = new MyFileInputStream("data.bin");
        MyBufferedInputStream buffered = new MyBufferedInputStream(raw);
        MyDataInputStream data = new MyDataInputStream(buffered);

        int value = data.readInt();
        System.out.println("读取的整数: " + value);
        data.close();
    }
}

4.2.2 实际 Java IO 使用示例

java
// 装饰器链:从一个文件读取,经过缓冲,再按行读取
try (BufferedReader reader = new BufferedReader(
         new InputStreamReader(
             new FileInputStream("test.txt"), StandardCharsets.UTF_8))) {

    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}

// 装饰器链分析:
// FileInputStream           → 节点流(读取原始字节)
// InputStreamReader         → 装饰器(字节→字符转换)
// BufferedReader            → 装饰器(加缓冲,提供 readLine)
// 每一层都增强了功能,但接口兼容(都是 Reader/InputStream 体系)

4.2.3 半透明装饰器

当装饰器需要提供额外方法时,称为"半透明装饰器"。

java
// 增强装饰器:提供额外方法
class CachedInputStream extends MyFilterInputStream {
    private List<Byte> cache = new ArrayList<>();
    private int readPos = 0;

    public CachedInputStream(MyInputStream in) {
        super(in);
    }

    @Override
    public int read() {
        int b = super.read();
        if (b != -1) cache.add((byte) b);
        return b;
    }

    // 额外方法:重置读取位置
    public void reset() {
        readPos = 0;
    }

    // 额外方法:获取已读取的所有字节
    public byte[] getCachedData() {
        byte[] result = new byte[cache.size()];
        for (int i = 0; i < cache.size(); i++) {
            result[i] = cache.get(i);
        }
        return result;
    }
}

4.3 生产级实现

Spring Boot 请求日志装饰器

java
// ============ HttpServletRequest 装饰器 ============
// 问题:HttpServletRequest 的 InputStream 只能读取一次
// 解决:装饰器模式包装,缓存请求体

class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
    private byte[] cachedBody;

    public CachedBodyHttpServletRequest(HttpServletRequest request) {
        super(request);
        try {
            InputStream inputStream = request.getInputStream();
            this.cachedBody = inputStream.readAllBytes();
        } catch (IOException e) {
            this.cachedBody = new byte[0];
        }
    }

    @Override
    public ServletInputStream getInputStream() {
        return new CachedBodyServletInputStream(this.cachedBody);
    }

    @Override
    public BufferedReader getReader() {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
        return new BufferedReader(new InputStreamReader(byteArrayInputStream));
    }

    public String getRequestBody() {
        return new String(cachedBody, StandardCharsets.UTF_8);
    }
}

class CachedBodyServletInputStream extends ServletInputStream {
    private final ByteArrayInputStream inputStream;

    public CachedBodyServletInputStream(byte[] cachedBody) {
        this.inputStream = new ByteArrayInputStream(cachedBody);
    }

    @Override public boolean isFinished() { return inputStream.available() == 0; }
    @Override public boolean isReady() { return true; }
    @Override public void setReadListener(ReadListener listener) {}
    @Override public int read() { return inputStream.read(); }
}

// ============ 请求日志过滤器 ============
@Component
class RequestLoggingFilter implements Filter {
    private static final Logger log = LoggerFactory.getLogger(RequestLoggingFilter.class);

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        CachedBodyHttpServletRequest cachedRequest =
            new CachedBodyHttpServletRequest((HttpServletRequest) request);

        log.info("请求 URI: {}", cachedRequest.getRequestURI());
        log.info("请求方法: {}", cachedRequest.getMethod());
        log.info("请求体: {}", cachedRequest.getRequestBody());

        chain.doFilter(cachedRequest, response);
    }
}

// ============ Spring Cache 装饰器 ============
// 为 Service 添加缓存装饰
@Service
class UserService {
    public User getUserById(Long id) {
        // 模拟耗时查询
        sleep(100);
        return new User(id, "张三");
    }

    private void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException e) {}
    }
}

// 缓存装饰器
@Component
class CachedUserService extends UserService {
    private final UserService delegate;
    private final Map<Long, User> cache = new ConcurrentHashMap<>();

    public CachedUserService(UserService delegate) {
        this.delegate = delegate;
    }

    @Override
    public User getUserById(Long id) {
        return cache.computeIfAbsent(id, delegate::getUserById);
    }
}

五、优缺点

优点

优点说明
灵活扩展比继承更灵活,可以运行时动态组合功能
避免类爆炸用组合代替继承,n 种功能只需 n 个装饰器类
符合开闭原则对扩展开放,对修改关闭
单一职责每个装饰器只关注一个功能,职责清晰
可组合装饰器可以任意组合,排列顺序影响最终行为

缺点

缺点说明
小对象多装饰链会产生大量小对象,增加内存开销
调试困难多层装饰链使得调用栈很深,排查问题复杂
初始化复杂客户端需要手动组装装饰链,代码冗长
类型丢失装饰后原始类型信息可能丢失(半透明装饰器例外)

六、适用场景

  1. 动态添加功能:需要在运行时给对象添加功能,而不影响其他对象。
  2. 功能组合:需要灵活组合多个功能,如数据流处理(缓冲+压缩+加密)。
  3. 不可继承的场景:类被 final 修饰,无法通过继承扩展。
  4. 撤销功能:装饰器可以动态添加和移除,适合实现撤销/重做。
  5. 权限控制:给不同对象动态添加权限检查。
  6. 日志/监控:给服务方法添加日志、性能监控等功能。

七、JDK / Spring 框架中的实际应用

7.1 Java IO 流(最经典)

java
// 字节流装饰器体系
InputStream is = new FileInputStream("file.txt");
is = new BufferedInputStream(is);        // 加缓冲
is = new DataInputStream(is);            // 读基本类型
is = new CheckedInputStream(is, new CRC32()); // 加校验

// 字符流装饰器体系
Reader reader = new FileReader("file.txt");
reader = new BufferedReader(reader);     // 加缓冲
reader = new LineNumberReader(reader);   // 加行号

// 对象流
ObjectInputStream ois = new ObjectInputStream(
    new BufferedInputStream(
        new FileInputStream("data.obj")));

7.2 Collections 工具类

java
// 同步装饰器
List<String> syncList = Collections.synchronizedList(new ArrayList<>());

// 不可变装饰器
List<String> unmodifiableList = Collections.unmodifiableList(new ArrayList<>());

// 类型安全装饰器
List<String> checkedList = Collections.checkedList(new ArrayList<>(), String.class);

7.3 Spring 中的装饰器

java
// 1. HttpServletRequestWrapper / HttpServletResponseWrapper
//    Servlet API 的请求/响应装饰器

// 2. BeanDefinitionDecorator
//    Spring 的 Bean 定义装饰器

// 3. TransactionAwareCacheDecorator
//    Spring Cache 的事务感知装饰器

// 4. ServerHttpRequestDecorator / ServerHttpResponseDecorator
//    Spring WebFlux 的请求/响应装饰器

八、与其他模式的关系

模式关系
适配器模式装饰器不改变接口,增强功能;适配器改变接口,不改变功能。装饰器支持递归组合,适配器通常不递归。
代理模式代理控制对对象的访问,装饰器增强对象的功能。结构上几乎相同,但意图不同。
组合模式装饰器只有一个子节点,组合有多个子节点。装饰器补充功能,组合统一处理整体和部分。
策略模式装饰器改变对象的"外表"(包装),策略模式改变对象的"内心"(算法)。
责任链模式两者结构相似(链式调用),但责任链中每个处理器可以终止请求,装饰器不会终止。

九、面试常见问题

Q1:装饰器模式和代理模式有什么区别?

  • 意图不同:装饰器强调增强功能,代理强调控制访问
  • 关注点不同:装饰器动态添加职责,代理控制对对象的访问(如延迟加载、权限检查)。
  • 实例化不同:装饰器通常由客户端显式组装,代理通常由框架自动生成(动态代理)。
  • 典型例子BufferedInputStream 是装饰器(增强功能),Spring AOP 是代理(控制访问)。

Q2:Java IO 中为什么使用装饰器模式而不是继承?

  • 如果用继承,每种功能组合都要一个子类(如 BufferedFileInputStreamDataFileInputStreamBufferedDataFileInputStream...),导致类爆炸。
  • 装饰器模式用组合方式,n 种功能只需 n 个装饰器类,可以任意组合。
  • 装饰器可以在运行时动态组合,而继承在编译时固定。

Q3:装饰器模式中的"半透明"是什么意思?

  • 透明装饰器:完全实现 Component 接口,不提供额外方法,客户端完全感知不到装饰器的存在。
  • 半透明装饰器:在实现 Component 接口之外,还提供额外方法(如 BufferedReader.readLine())。客户端需要知道具体装饰器类型才能使用额外方法,失去了一部分透明性。

Q4:装饰器模式中,装饰顺序重要吗?

:非常重要。装饰顺序不同,行为可能完全不同。例如:

java
// 先缓冲再加密:数据先进入缓冲区,再加密写入
OutputStream out = new EncryptOutputStream(new BufferedOutputStream(new FileOutputStream("a.dat")));
// 先加密再缓冲:数据先加密,再进入缓冲区
OutputStream out = new BufferedOutputStream(new EncryptOutputStream(new FileOutputStream("a.dat")));

Q5:如何在 Spring Boot 中使用装饰器模式?

:常见场景:

  1. 请求体缓存:使用 HttpServletRequestWrapper 装饰请求,缓存请求体以便多次读取。
  2. 响应体包装:使用 HttpServletResponseWrapper 装饰响应,捕获响应内容进行日志记录。
  3. 缓存装饰:为 Service 创建缓存装饰器,在调用原方法前后添加缓存逻辑。
  4. Spring Cache 的 TransactionAwareCacheDecorator 就是装饰器模式的应用。