装饰器模式 - 咖啡店点单
装饰器模式定义
动态地给对象添加额外功能,而不改变其结构。通过创建装饰类来包装原始对象,装饰类与原对象实现相同接口,可以在调用原对象方法前后添加新行为。支持功能的层层嵌套和灵活组合,是继承的替代方案,遵循开闭原则。
核心:对象包装 + 功能增强 + 接口不变
需求场景
咖啡店有基础咖啡(美式、拿铁),顾客可以加各种配料(牛奶、糖、摩卡、奶油)。
❌ 如果用继承会怎样?
scala
// 基础咖啡
class Americano { }
class Latte { }
// 加牛奶
class AmericanoWithMilk extends Americano { }
class LatteWithMilk extends Latte { }
// 加牛奶+糖
class AmericanoWithMilkAndSugar extends Americano { }
class LatteWithMilkAndSugar extends Latte { }
// 加牛奶+糖+摩卡
class AmericanoWithMilkAndSugarAndMocha extends Americano { }
// ...
// 😱 2种咖啡 × 4种配料 = 至少需要 30+ 个类!
问题:
- 配料组合太多,类爆炸
- 新增配料要改所有咖啡类
- 无法动态调整(顾客不要糖了?要重新点单)
✅ 装饰器模式实现
java
// ============ 1. 核心接口 ============
interface Coffee {
String getDescription(); // 描述
double getCost(); // 价格
}
// ============ 2. 基础咖啡(被装饰的对象) ============
class Americano implements Coffee {
@Override
public String getDescription() {
return "美式咖啡";
}
@Override
public double getCost() {
return 15.0;
}
}
class Latte implements Coffee {
@Override
public String getDescription() {
return "拿铁咖啡";
}
@Override
public double getCost() {
return 20.0;
}
}
// ============ 3. 装饰器基类 ============
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee; // 持有被装饰的对象
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
// ============ 4. 具体装饰器(各种配料) ============
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + " + 牛奶";
}
@Override
public double getCost() {
return coffee.getCost() + 3.0;
}
}
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + " + 糖";
}
@Override
public double getCost() {
return coffee.getCost() + 1.0;
}
}
class MochaDecorator extends CoffeeDecorator {
public MochaDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + " + 摩卡";
}
@Override
public double getCost() {
return coffee.getCost() + 5.0;
}
}
class WhipDecorator extends CoffeeDecorator {
public WhipDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + " + 奶油";
}
@Override
public double getCost() {
return coffee.getCost() + 4.0;
}
}
// ============ 5. 使用示例 ============
public class CoffeeShop {
public static void main(String[] args) {
// 顾客1:点一杯纯美式
Coffee order1 = new Americano();
System.out.println(order1.getDescription() + " = ¥" + order1.getCost());
// 输出:美式咖啡 = ¥15.0
System.out.println("----------");
// 顾客2:美式 + 牛奶
Coffee order2 = new Americano();
order2 = new MilkDecorator(order2);
System.out.println(order2.getDescription() + " = ¥" + order2.getCost());
// 输出:美式咖啡 + 牛奶 = ¥18.0
System.out.println("----------");
// 顾客3:拿铁 + 牛奶 + 糖 + 摩卡 + 奶油(豪华版)
Coffee order3 = new Latte();
order3 = new MilkDecorator(order3);
order3 = new SugarDecorator(order3);
order3 = new MochaDecorator(order3);
order3 = new WhipDecorator(order3);
System.out.println(order3.getDescription() + " = ¥" + order3.getCost());
// 输出:拿铁咖啡 + 牛奶 + 糖 + 摩卡 + 奶油 = ¥33.0
System.out.println("----------");
// 顾客4:美式 + 双倍糖(可以重复装饰)
Coffee order4 = new Americano();
order4 = new SugarDecorator(order4);
order4 = new SugarDecorator(order4); // 再加一份糖
System.out.println(order4.getDescription() + " = ¥" + order4.getCost());
// 输出:美式咖啡 + 糖 + 糖 = ¥17.0
}
}
运行结果
markdown
美式咖啡 = ¥15.0
----------
美式咖啡 + 牛奶 = ¥18.0
----------
拿铁咖啡 + 牛奶 + 糖 + 摩卡 + 奶油 = ¥33.0
----------
美式咖啡 + 糖 + 糖 = ¥17.0
装饰器模式的核心逻辑(像套娃)
scss
🎁 最外层装饰器.process()
↓ 调用
🎁 第二层装饰器.process()
↓ 调用
🎁 第三层装饰器.process()
↓ 调用
☕ 核心对象.process()
具体例子:
scss
// 顾客点:拿铁 + 牛奶 + 糖
Coffee coffee = new Latte(); // ☕ 核心:拿铁
coffee = new MilkDecorator(coffee); // 🎁 第一层:牛奶包装拿铁
coffee = new SugarDecorator(coffee); // 🎁 第二层:糖包装(牛奶+拿铁)
// 调用 coffee.getCost() 时:
// 1. SugarDecorator.getCost() → 调用内层 + 1.0
// 2. MilkDecorator.getCost() → 调用内层 + 3.0
// 3. Latte.getCost() → 返回 20.0
// 结果:20.0 + 3.0 + 1.0 = 24.0
对比继承和装饰器
场景 | 继承方案 | 装饰器方案 |
---|---|---|
新增配料(加焦糖) | 每种咖啡都要加子类 | 只需加 CaramelDecorator |
双倍糖 | 要创建专门的子类 | 装饰两次 new SugarDecorator(new SugarDecorator(coffee)) |
配料顺序 | 无法实现 | 自由控制装饰顺序 |
类的数量 | 指数增长(2×4×3...) | 线性增长(2+4) |
真实场景类比
1. Java IO流(装饰器经典案例)
ini
// 基础流
InputStream input = new FileInputStream("file.txt");
// 加缓冲装饰
input = new BufferedInputStream(input);
// 加数据类型转换装饰
DataInputStream dataInput = new DataInputStream(input);
// 层层装饰,功能叠加
2. 游戏角色装备系统
scss
// 基础角色
Character hero = new Warrior();
// 穿装备(装饰)
hero = new ArmorDecorator(hero); // 穿盔甲 +50防御
hero = new SwordDecorator(hero); // 拿剑 +30攻击
hero = new BootsDecorator(hero); // 穿靴子 +10速度
System.out.println("攻击力:" + hero.getAttack());
System.out.println("防御力:" + hero.getDefense());
3. Web请求处理
scss
// 基础处理器
RequestHandler handler = new BasicHandler();
// 加日志装饰
handler = new LoggingDecorator(handler);
// 加认证装饰
handler = new AuthDecorator(handler);
// 加缓存装饰
handler = new CacheDecorator(handler);
// 请求会依次经过:缓存检查 → 认证 → 日志 → 实际处理
装饰器 vs 其他模式
模式 | 目的 | 关键区别 |
---|---|---|
装饰器 | 动态添加功能 | 保持接口不变,功能叠加 |
适配器 | 接口转换 | 改变接口,不增加功能 |
代理 | 控制访问 | 代理类控制目标对象 |
继承 | 静态扩展 | 编译时确定,无法动态组合 |
总结
装饰器模式的本质:
- 像穿衣服一样层层包装
- 每层都能增强功能(加价格、加描述)
- 不改变核心对象(咖啡还是咖啡)
- 灵活组合(想加几层加几层)
记住公式:
scss
对象 = 装饰器3( 装饰器2( 装饰器1( 核心对象 ) ) )