用装饰者模式给咖啡店订单系统计费?

wen java案例 69

本文目录导读:

用装饰者模式给咖啡店订单系统计费?

  1. 核心设计思路
  2. 代码实现
  3. 运行结果
  4. 装饰者模式的关键点总结(针对此案例)
  5. 扩展思考:如果是实际项目

这是一个经典的装饰者模式(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

装饰者模式的关键点总结(针对此案例)

  1. 类型兼容CondimentDecorator 继承自 Beverage,因此调料可以包装任何咖啡,并且返回的仍然是 Beverage 类型。
  2. 递归调用Mocha.cost() 会先调用 beverage.cost()(可能是另一个调料或基础咖啡),然后加上自己的价格,这样就实现了价格累加。
  3. 动态叠加:你可以在运行时灵活地组合任意数量的调料,无需为每种组合(如“双份摩卡拿铁”、“加豆浆和奶泡的美式”)写专门的类。
  4. 符合开闭原则:如果新增一种调料(肉桂粉”),只需新增一个 Cinnamon 类,无需修改现有咖啡或调料类。

扩展思考:如果是实际项目

在实际项目中,你可能还需要考虑以下几点:

  • 类型安全:有些咖啡可能不允许加某些特定调料(美式咖啡不能加牛奶”),你可以通过修改装饰者的逻辑或引入额外的校验来解决。
  • 大小规格:咖啡通常还有小、中、大杯之分,价格不同,你可以将大小作为基类中的一个属性或子类来处理,然后在装饰者中根据大小计算不同价格。
  • 多语言 / 上品名:描述字符串的管理可以抽离到配置或枚举中。

装饰者模式在这个场景下非常优雅地解决了“功能组合”问题,是经典设计模式中实用性很强的一个。

抱歉,评论功能暂时关闭!