You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

赛百味复刻项目中订单配料有效性与顺序验证的代码优化方案咨询

我完全懂你这种看着一堆嵌套条件判断越写越头大的感觉——面条代码不仅难维护,后续要加新规则或者调整配料顺序简直是噩梦。咱们来把这个验证逻辑拆得更清晰、更易扩展,同时保留你用HashSet提升查找效率的优点。

核心思路:用「阶段化状态机」替代嵌套判断

你的订单规则是严格按顺序分阶段的,刚好可以把每个阶段的要求(必选/可选、数量范围、对应菜单分类)抽象出来,然后用线性遍历的方式逐个阶段验证,避免嵌套。


第一步:把规则集中管理

先把每个阶段的规则定义成枚举,这样后续改规则(比如调整配料数量上限)直接改枚举就行,不用动验证逻辑:

enum IngredientStage {
    BREAD(1, 1, "Bread"),   // 必选1份,对应菜单的Bread分类
    MEAT(0, 1, "Meat"),     // 可选0-1份,对应菜单的Meat分类
    CHEESE(1, 1, "Cheese"), // 必选1份,对应菜单的Cheese分类
    EXTRAS(1, 3, "Extras"), // 必选1-3份,对应菜单的Extras分类
    SAUCES(1, 3, "Sauces"); // 必选1-3份,对应菜单的Sauces分类

    private final int minRequired;
    private final int maxAllowed;
    private final String menuCategoryKey;

    IngredientStage(int min, int max, String menuKey) {
        this.minRequired = min;
        this.maxAllowed = max;
        this.menuCategoryKey = menuKey;
    }

    // Getter方法
    public int getMinRequired() { return minRequired; }
    public int getMaxAllowed() { return maxAllowed; }
    public String getMenuCategoryKey() { return menuCategoryKey; }
}

第二步:实现线性的阶段验证逻辑

遍历订单配料时,我们只需要跟踪当前处于哪个阶段、当前阶段已选了多少配料,遇到不属于当前阶段的配料时,先检查当前阶段是否满足最低要求,再切换到下一个阶段验证:

boolean isValid(List<String> ingredients) {
    // 先校验总数量的边界值
    if (ingredients.size() < 4 || ingredients.size() > 9) {
        return false;
    }

    IngredientStage[] stages = IngredientStage.values();
    int currentStageIdx = 0;
    int countInCurrentStage = 0;

    for (String ingredient : ingredients) {
        IngredientStage currentStage = stages[currentStageIdx];
        Set<String> validItems = Restaurant.menu.get(currentStage.getMenuCategoryKey());

        // 当前配料属于当前阶段的合法选项
        if (validItems.contains(ingredient)) {
            countInCurrentStage++;
            // 检查是否超过当前阶段的最大允许数量
            if (countInCurrentStage > currentStage.getMaxAllowed()) {
                System.out.println("错误:" + currentStage.name().toLowerCase() +  "数量超出上限");
                return false;
            }
        } else {
            // 不属于当前阶段,尝试切换到下一个阶段
            currentStageIdx++;
            // 先检查当前阶段是否满足最低要求
            if (currentStageIdx >= stages.length || countInCurrentStage < currentStage.getMinRequired()) {
                System.out.println("错误:" + currentStage.name().toLowerCase() + "数量未达要求");
                return false;
            }
            // 验证新阶段是否包含该配料
            IngredientStage nextStage = stages[currentStageIdx];
            validItems = Restaurant.menu.get(nextStage.getMenuCategoryKey());
            if (!validItems.contains(ingredient)) {
                System.out.println("错误:非法配料 " + ingredient);
                return false;
            }
            // 新阶段计数从1开始
            countInCurrentStage = 1;
        }
    }

    // 最后检查最后一个阶段是否满足最低要求
    IngredientStage lastStage = stages[currentStageIdx];
    if (countInCurrentStage < lastStage.getMinRequired()) {
        System.out.println("错误:" + lastStage.name().toLowerCase() + "数量未达要求");
        return false;
    }

    // 确保所有阶段都按顺序走完(比如不能只选到Extras就结束,必须到Sauces)
    if (currentStageIdx != stages.length - 1) {
        System.out.println("错误:缺少必填阶段 " + stages[currentStageIdx + 1].name().toLowerCase());
        return false;
    }

    return true;
}

额外优化建议

  1. 替换打印为自定义异常:把System.out.println换成自定义的InvalidOrderException,携带具体错误信息,这样上层代码可以更灵活地处理错误(比如返回友好提示给用户)。
  2. 提取辅助方法:把“检查配料是否属于某个菜单分类”提取成private boolean isIngredientValid(String ingredient, String category),减少重复代码,提升可读性。
  3. 添加空指针防护:确保Restaurant.menu的每个分类都提前初始化了HashSet,避免get()返回null导致空指针。
  4. 补充单元测试:针对每个阶段的边界情况(比如跳过肉类阶段、刚好选3份Extras、总数量刚好4/9等)写单元测试,确保逻辑稳定。

另外,你选择用HashSet存储菜单的思路非常正确——O(1)的查找效率比ArrayList的O(n)高效多了,完全不用改这部分。

内容的提问来源于stack exchange,提问作者Deutrys

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 00:32:40