本文目录导读:

这是一个经典的装饰者模式(Decorator Pattern)教学案例,装饰者模式非常适合解决“对象功能动态叠加”的问题,并且能避免“类爆炸”(比如为每种咖啡+调料组合都创建一个子类)。
下面我会用 Java 代码演示如何用装饰者模式实现咖啡店订单系统的计费。
核心设计思路
- 抽象组件(Component):定义一个抽象的咖啡接口或抽象类,包含
getDescription()(获取描述)和cost()(计算价格)方法。 - 具体组件(Concrete Component):具体的咖啡种类,如“美式咖啡”、“拿铁咖啡”。
- 装饰者(Decorator):一个抽象类,继承自组件接口,并持有组件的引用,其子类是具体的调料,如“牛奶”、“摩卡”、“奶泡”。
代码实现
步骤 1:定义抽象组件(咖啡饮料)
// Beverage.java
public abstract class Beverage {
protected String description = "Unknown Beverage";
public String getDescription() {
return description;
}
// 抽象方法,子类必须实现
public abstract double cost();
}
步骤 2:定义具体组件(基础咖啡)
// Espresso.java - 浓缩咖啡
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
}
@Override
public double cost() {
return 1.99; // 基础价格
}
}
// HouseBlend.java - 首选咖啡(一种综合咖啡)
public class HouseBlend extends Beverage {
public HouseBlend() {
description = "House Blend Coffee";
}
@Override
public double cost() {
return 0.89;
}
}
// DarkRoast.java - 深焙咖啡
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "Dark Roast Coffee";
}
@Override
public double cost() {
return 0.99;
}
}
步骤 3:定义抽象装饰者(调料基类)
关键点:装饰者必须继承自 Beverage,并且内部包装一个 Beverage 对象。
// CondimentDecorator.java
public abstract class CondimentDecorator extends Beverage {
// 持有被装饰者的引用
protected Beverage beverage;
// 抽象方法:让具体调料返回带调料的描述
public abstract String getDescription();
}
步骤 4:定义具体装饰者(各种调料)
// Mocha.java - 摩卡(巧克力)
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
@Override
public double cost() {
// 计算:被装饰者的原价 + 摩卡调料价格
return beverage.cost() + 0.20;
}
}
// Whip.java - 奶泡
public class Whip extends CondimentDecorator {
public Whip(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Whip";
}
@Override
public double cost() {
return beverage.cost() + 0.10;
}
}
// Soy.java - 豆浆
public class Soy extends CondimentDecorator {
public Soy(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Soy";
}
@Override
public double cost() {
return beverage.cost() + 0.15;
}
}
步骤 5:测试订单系统
// CoffeeTest.java
public class CoffeeTest {
public static void main(String[] args) {
// 1. 点一杯浓缩咖啡,不加任何调料
Beverage espresso = new Espresso();
System.out.println(espresso.getDescription() + " $" + espresso.cost());
// 2. 点一杯首选咖啡,加双份摩卡、加奶泡
Beverage houseBlend = new HouseBlend();
houseBlend = new Mocha(houseBlend); // 加一份摩卡
houseBlend = new Mocha(houseBlend); // 再加一份摩卡
houseBlend = new Whip(houseBlend); // 加奶泡
System.out.println(houseBlend.getDescription() + " $" + houseBlend.cost());
// 3. 点一杯深焙咖啡,加豆浆、摩卡、奶泡
Beverage darkRoast = new DarkRoast();
darkRoast = new Soy(darkRoast);
darkRoast = new Mocha(darkRoast);
darkRoast = new Whip(darkRoast);
System.out.println(darkRoast.getDescription() + " $" + darkRoast.cost());
}
}
运行结果
Espresso $1.99
House Blend Coffee, Mocha, Mocha, Whip $1.39
Dark Roast Coffee, Soy, Mocha, Whip $1.44
装饰者模式的关键点总结(针对此案例)
- 类型兼容:
CondimentDecorator继承自Beverage,因此调料可以包装任何咖啡,并且返回的仍然是Beverage类型。 - 递归调用:
Mocha.cost()会先调用beverage.cost()(可能是另一个调料或基础咖啡),然后加上自己的价格,这样就实现了价格累加。 - 动态叠加:你可以在运行时灵活地组合任意数量的调料,无需为每种组合(如“双份摩卡拿铁”、“加豆浆和奶泡的美式”)写专门的类。
- 符合开闭原则:如果新增一种调料(肉桂粉”),只需新增一个
Cinnamon类,无需修改现有咖啡或调料类。
扩展思考:如果是实际项目
在实际项目中,你可能还需要考虑以下几点:
- 类型安全:有些咖啡可能不允许加某些特定调料(美式咖啡不能加牛奶”),你可以通过修改装饰者的逻辑或引入额外的校验来解决。
- 大小规格:咖啡通常还有小、中、大杯之分,价格不同,你可以将大小作为基类中的一个属性或子类来处理,然后在装饰者中根据大小计算不同价格。
- 多语言 / 上品名:描述字符串的管理可以抽离到配置或枚举中。
装饰者模式在这个场景下非常优雅地解决了“功能组合”问题,是经典设计模式中实用性很强的一个。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播