装饰器模式 (Decorator)
场景问题
问题案例
考虑这样一个实际的业务场景:咖啡订单系统。用户可以点一杯基础咖啡,也可以选择加牛奶、加糖、加巧克力等配料。每种配料都可以叠加,最终需要计算总价并输出描述。
不用模式的解决方案
实现简化版本
最直接的做法是为每种组合写一个类,例如:
public class Coffee {
public String getDescription() { return "咖啡"; }
public double cost() { return 10.0; }
}
public class CoffeeWithMilk extends Coffee {
@Override
public String getDescription() { return super.getDescription() + "+牛奶"; }
@Override
public double cost() { return super.cost() + 2.0; }
}
public class CoffeeWithMilkAndSugar extends CoffeeWithMilk {
@Override
public String getDescription() { return super.getDescription() + "+糖"; }
@Override
public double cost() { return super.cost() + 1.0; }
}
使用示例
public class Client {
public static void main(String[] args) {
Coffee coffee = new CoffeeWithMilkAndSugar();
System.out.println(coffee.getDescription() + " 总价:" + coffee.cost());
}
}
有何问题
这种实现方式存在明显问题:
- 每增加一种配料组合都要写一个新类,类爆炸。
- 组合不灵活,不能动态叠加。
- 代码重复,维护困难。
解决方案
装饰器模式的定义
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式比生成子类更为灵活。
应用装饰器模式的思路
- 定义一个统一的组件接口(如Beverage),基础对象和装饰器都实现该接口。
- 装饰器持有一个组件对象的引用,并在扩展功能时调用原有对象的方法。
- 可以动态、灵活地叠加多个装饰器。
装饰器模式的结构
- Beverage:抽象组件,定义统一接口。
- Coffee:具体组件,实现基础功能。
- Decorator:抽象装饰器,持有组件引用,扩展功能。
- Milk/Sugar:具体装饰器,实现具体的附加功能。
装饰器模式的实现
// 统一接口
public interface Beverage {
String getDescription();
double cost();
}
// 具体组件
public class Coffee implements Beverage {
public String getDescription() { return "咖啡"; }
public double cost() { return 10.0; }
}
// 抽象装饰器
public abstract class Decorator implements Beverage {
protected Beverage beverage;
public Decorator(Beverage beverage) { this.beverage = beverage; }
}
// 具体装饰器:牛奶
public class Milk extends Decorator {
public Milk(Beverage beverage) { super(beverage); }
public String getDescription() { return beverage.getDescription() + "+牛奶"; }
public double cost() { return beverage.cost() + 2.0; }
}
// 具体装饰器:糖
public class Sugar extends Decorator {
public Sugar(Beverage beverage) { super(beverage); }
public String getDescription() { return beverage.getDescription() + "+糖"; }
public double cost() { return beverage.cost() + 1.0; }
}
使用示例
public class Client {
public static void main(String[] args) {
Beverage coffee = new Coffee();
coffee = new Milk(coffee); // 加牛奶
coffee = new Sugar(coffee); // 加糖
System.out.println(coffee.getDescription() + " 总价:" + coffee.cost());
}
}
装饰器重写示例
示例结构
用mermaid画出装饰器叠加的结构关系:
示例代码
见上文"装饰器模式的实现"部分。
模式讲解
装饰器模式的功能
- 动态、灵活地为对象添加新功能。
- 可以任意组合、叠加装饰器。
- 不影响原有类结构,符合开闭原则。
实现成接口
- 抽象组件通常定义为接口或抽象类。
- 装饰器和具体组件都实现统一接口。
使用工厂方法
- 可结合工厂方法模式,统一创建装饰器链。
切换产品簇
- 装饰器模式可用于切换不同的功能组合,如不同饮品配料。