拿咖啡店举个例子。
一杯美式 10 元,加牛奶 2 元,加糖 1 元。有人只加牛奶,也有人两样都要。
搭配不复杂,但很适合拿来理解装饰器模式。

每种搭配都写一个类,有点顶不住
刚开始可能只有 Americano,后来又有牛奶美式、加糖美式、牛奶加糖美式。
继续加奶油、冰块,类名就开始往外冒。
这写法能跑,但搭配越多,类越多。改个价格,也得来回找。
装饰器模式换了个思路:保留原来的咖啡,需要什么配料,就在外面包一层。
先看关键 UML 图
类图里最重要的是 CoffeeDecorator 手里还拿着一个 Coffee。
Americano 是原始对象。
MilkDecorator 和 SugarDecorator 是装饰器。它们自己也是 Coffee,同时又包着另一个 Coffee。
看着有点绕,先记住:外面那层和里面那层,用的是同一个接口。
先准备一杯普通咖啡
接口只放两个操作:描述和价格。
public interface Coffee {
String getDescription();
int getPrice();
}
美式咖啡是最里面那一层。
public class Americano implements Coffee {
public String getDescription() {
return "美式咖啡";
}
public int getPrice() {
return 10;
}
}
到这里还没用设计模式,就是一杯 10 元的普通美式。
装饰器先把咖啡接住
所有配料装饰器都有一个共同点:内部要保存被包装的咖啡。
public abstract class CoffeeDecorator implements Coffee {
protected final Coffee coffee;
protected CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
CoffeeDecorator 也实现了 Coffee。所以外面的代码,照样能把它当成咖啡用。
牛奶是在原价上加 2 元
牛奶装饰器先问里面那杯咖啡多少钱,再加 2 元。
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + " + 牛奶";
}
public int getPrice() {
return coffee.getPrice() + 2;
}
}
它没有改 Americano,只是从外面包了一层。
糖装饰器也差不多,只是价格加 1 元,描述后面加上“糖”。
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + " + 糖";
}
public int getPrice() {
return coffee.getPrice() + 1;
}
}
一层一层包起来
实际使用时,先创建美式,再包牛奶,外面接着包糖。
Coffee coffee = new Americano();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription());
System.out.println("价格:" + coffee.getPrice() + "元");
输出:
美式咖啡 + 牛奶 + 糖
价格:13元
最外层是 SugarDecorator。它先往里问,问到 MilkDecorator,再问到 Americano。
里面返回 10 元,牛奶加 2 元,糖再加 1 元,算出来是 13 元。
青岛话说,这个顺序捋顺了,装饰器就不难了。
它和继承有啥区别
继承通常在写代码时,就把功能固定在类里面。
装饰器可以在程序运行时自由搭配。只加糖、只加奶,或者两样都加,都不用再写咖啡组合类。
不过也别把继承一棍子打死。类型关系稳定、功能不需要随意叠加时,普通继承更直白。
包装顺序会不会影响结果
这杯咖啡里,先加奶再加糖,和先加糖再加奶,价格都是 13 元。
但现实项目不一定。比如先压缩再加密,和先加密再压缩,结果可能不同。
所以装饰器可以自由组合,不代表顺序永远无所谓。这一点得看每层具体做了什么。
啥时候适合用装饰器
功能能一层层叠加,又不想总改原来的类,可以考虑装饰器。
Java 的输入输出流就用了类似思路。基础流外面可以继续包装缓冲、数据读取等能力。
这篇先不展开源码,小白先把咖啡这层想明白就行。
如果功能固定,也没几种搭配,写一个普通方法更省事。包装七八层以后,排查问题也容易犯迷糊。
狗哥收个尾
装饰器模式的重点,是 用包装给对象增加功能。
原始咖啡不动,牛奶和糖各管一层。所有对象都实现 Coffee,所以可以像套杯子一样,一层接一层地组合。
下一篇继续聊 外观模式。家里一堆设备开机太麻烦,咱给它们做个“一键开影院”。