Skip to content

装饰器模式是"组合优于继承"的最佳示范,Java 的 IO 流、Spring 里到处是它的影子。这篇把它讲清楚:为什么不用继承、装饰器怎么"层层包装"、以及它和代理模式这对"孪生兄弟"到底差在哪。

一、解决什么问题

装饰器模式解决的是**"给对象动态加功能,又不改原类、不搞继承爆炸"**的问题。

假设你要卖咖啡,有基础咖啡,可以加牛奶、加糖、加奶泡。如果用继承

Coffee
├─ MilkCoffee
├─ SugarCoffee
├─ MilkSugarCoffee      ← 每种组合都要一个子类
├─ MilkFoamCoffee
└─ ...(组合爆炸)

每多一种配料,子类就成倍增长,而且运行期没法动态组合(不能"先加奶再加糖"这种任意顺序)。

装饰器模式用组合 + 包装解决:把"加料"做成一个个装饰器,像洋葱一样一层层包上去。

二、装饰器模式怎么落地

java
// ① 抽象组件
public interface Coffee {
    double cost();
    String desc();
}

// ② 具体组件:基础咖啡
public class SimpleCoffee implements Coffee {
    public double cost() { return 10; }
    public String desc() { return "咖啡"; }
}

// ③ 装饰器基类:实现同一接口 + 持有被装饰对象
public abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;
    public CoffeeDecorator(Coffee coffee) { this.coffee = coffee; }
}

// ④ 具体装饰器:加牛奶、加糖
public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) { super(coffee); }
    public double cost() { return coffee.cost() + 2; }          // 在原来基础上加钱
    public String desc() { return coffee.desc() + "+牛奶"; }     // 在原来描述上追加
}
public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) { super(coffee); }
    public double cost() { return coffee.cost() + 1; }
    public String desc() { return coffee.desc() + "+糖"; }
}

// 使用:层层包装,任意顺序组合
Coffee coffee = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
System.out.println(coffee.desc());   // 咖啡+牛奶+糖
System.out.println(coffee.cost());   // 13

核心三要素

  • 装饰器和被装饰对象实现同一个接口(所以能层层嵌套)
  • 装饰器持有一个被装饰对象的引用(组合)
  • 在调用被装饰对象方法前后,附加新功能(加钱、追加描述)

装饰器模式结构:装饰器与被装饰对象实现同一接口,层层包装

三、实际应用

Java IO 流是装饰器模式的教科书

java
// FileInputStream 是最基础的"具体组件"
// BufferedInputStream 是装饰器,加缓冲
// DataInputStream 是装饰器,加类型化读取
DataInputStream in = new DataInputStream(
    new BufferedInputStream(
        new FileInputStream("data.bin")
    )
);

每一层 new 都是套一层装饰器,各管各的功能(缓冲、类型化读取),组合出不同能力。这就是为什么 IO 流能"套娃"的原因。

四、装饰器 vs 代理(易混,重点)

两者代码结构几乎一模一样(都持有对象 + 实现同接口),但目的不同

维度装饰器模式代理模式
目的增强功能(加新能力)控制访问(延迟加载/权限/远程)
关注让对象"能做更多事"让对象"能不能被访问"
创建通常由使用方层层包装通常由框架自动生成
典型IO 流加缓冲、加类型Spring AOP、延迟加载

一句话:装饰器是"锦上添花",代理是"门前守卫"。代理模式的详解见《代理模式》(文章整理中)。

小结

  • 装饰器解决"动态加功能 + 继承爆炸",核心是"组合优于继承"
  • 三要素:同接口 + 持有被装饰对象 + 前后附加功能
  • 典型:Java IO 流套娃、Collections 装饰器
  • 与代理区别:装饰器"增强",代理"控制访问"

下一步可以看《代理模式》——它和装饰器长得几乎一样,但目的是"控制访问",Spring AOP 就是动态代理的集大成者。