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

59 阅读4分钟

7.装饰器模式 (Decorator) - 咖啡店点单

装饰器模式定义

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

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

需求场景

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

❌ 如果用继承会怎样?

// 基础咖啡
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+ 个类!

问题

  • 配料组合太多,类爆炸
  • 新增配料要改所有咖啡类
  • 无法动态调整(顾客不要糖了?要重新点单)

✅ 装饰器模式实现

// ============ 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
    }
}

运行结果

美式咖啡 = ¥15.0
----------
美式咖啡 + 牛奶 = ¥18.0
----------
拿铁咖啡 + 牛奶 + 糖 + 摩卡 + 奶油 = ¥33.0
----------
美式咖啡 + 糖 + 糖 = ¥17.0

对比继承和装饰器

场景继承方案装饰器方案
新增配料(加焦糖)每种咖啡都要加子类只需加 CaramelDecorator
双倍糖要创建专门的子类装饰两次 new SugarDecorator(new SugarDecorator(coffee))
配料顺序无法实现自由控制装饰顺序
类的数量指数增长(2×4×3...)线性增长(2+4)

真实场景类比

1. Java IO流(装饰器经典案例)

// 基础流
InputStream input = new FileInputStream("file.txt");

// 加缓冲装饰
input = new BufferedInputStream(input);

// 加数据类型转换装饰
DataInputStream dataInput = new DataInputStream(input);

// 层层装饰,功能叠加

2. 游戏角色装备系统

// 基础角色
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请求处理

// 基础处理器
RequestHandler handler = new BasicHandler();
// 加日志装饰
handler = new LoggingDecorator(handler);
// 加认证装饰
handler = new AuthDecorator(handler);
// 加缓存装饰
handler = new CacheDecorator(handler);
// 请求会依次经过:缓存检查 → 认证 → 日志 → 实际处理

装饰器 vs 其他模式

模式目的关键区别
装饰器动态添加功能保持接口不变,功能叠加
适配器接口转换改变接口,不增加功能
代理控制访问代理类控制目标对象
继承静态扩展编译时确定,无法动态组合

总结

装饰器模式的本质

  • 像穿衣服一样层层包装
  • 每层都能增强功能(加价格、加描述)
  • 不改变核心对象(咖啡还是咖啡)
  • 灵活组合(想加几层加几层)