23种设计模式-深度讲解-7. 装饰器模式 (Decorator)

装饰器模式 - 咖啡店点单

装饰器模式定义

动态地给对象添加额外功能,而不改变其结构。通过创建装饰类来包装原始对象,装饰类与原对象实现相同接口,可以在调用原对象方法前后添加新行为。支持功能的层层嵌套和灵活组合,是继承的替代方案,遵循开闭原则。

核心:对象包装 + 功能增强 + 接口不变

需求场景

咖啡店有基础咖啡(美式、拿铁),顾客可以加各种配料(牛奶、糖、摩卡、奶油)。

❌ 如果用继承会怎样?

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( 核心对象 ) ) )
相关推荐
LCG元5 小时前
Docker容器化实战:将你的SpringBoot应用一键打包部署,告别环境不一致的烦恼!#第一部分
后端·docker
初级程序员Kyle5 小时前
Java复习日记 - 第一天:重拾Java基础
后端
oak隔壁找我5 小时前
SpringBoot 开发必备基础工具类实现(纯JWT认证,无SpringSecurity)
java·后端
张较瘦_5 小时前
Springboot | 初识Springboot 从“手动做饭”到“点外卖”的编程革命
java·spring boot·后端
间彧5 小时前
举例说明混合使用CAS和传统锁机制的成功案例
后端
间彧5 小时前
在高并发场景下,如何评估是使用CAS还是传统锁机制更合适?
后端
间彧5 小时前
在高并发场景下,如何量化评估何时应该从CAS切换到传统锁机制?
后端
oak隔壁找我5 小时前
SpringBoot 整合 Minio 和 FastDFS 实现分布式文件存储
java·后端
间彧5 小时前
CAS技术原理与应用详解
后端