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

如何用委托与枚举实现强制顺序的Step Builder模式?

Hey,这个需求用Step Builder模式 + 枚举状态机 + 委托来实现简直是完美匹配!既能强制要求用户按顺序调用必填方法,又能灵活支持可选方法的多次调用,而且代码结构清晰,还能在编译期就防止错误调用。我给你一步步拆解实现思路和完整代码:

核心思路

  1. 用枚举做状态管理:定义构建的各个阶段(比如需要设置value、需要设置anotherValue、可以添加字段),跟踪当前处于哪个步骤,确保调用顺序正确。
  2. 用阶段接口做类型限制:每个阶段对应一个接口,只暴露当前阶段允许调用的方法,这样用户在编译期就只能调用符合顺序的方法,跳步直接编译报错。
  3. 用委托类处理实际逻辑:写一个内部Builder实现类,实现所有阶段接口,负责实际的字段赋值和状态切换,对外只暴露当前阶段的接口类型,隐藏内部细节。

完整代码实现

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class MyObject {
    // 最终对象的字段
    private final String value;
    private final String anotherValue;
    private final Map<String, String> fields;

    // 私有构造方法,只能通过Builder创建
    private MyObject(Builder builder) {
        this.value = builder.value;
        this.anotherValue = builder.anotherValue;
        this.fields = Collections.unmodifiableMap(new HashMap<>(builder.fields));
    }

    // 对外提供getter
    public String getValue() { return value; }
    public String getAnotherValue() { return anotherValue; }
    public String getField(String key) { return fields.get(key); }

    // 构建入口:返回第一个阶段的接口
    public static ValueStep builder() {
        return new MyObjectBuilderImpl();
    }

    // ------------------------------
    // 1. 枚举:标记当前构建阶段
    // ------------------------------
    private enum BuildStep {
        NEED_VALUE,       // 等待设置value
        NEED_ANOTHER_VALUE,// 等待设置anotherValue
        CAN_ADD_FIELDS    // 可添加字段(可选)
    }

    // ------------------------------
    // 2. 阶段接口:强制暴露当前阶段允许的方法
    // ------------------------------
    public interface ValueStep {
        AnotherValueStep withValue(String value);
    }

    public interface AnotherValueStep {
        FieldStep withAnotherValue(String anotherValue);
    }

    public interface FieldStep {
        FieldStep withField(String key, String value);
        MyObject build();
    }

    // ------------------------------
    // 3. 核心Builder实现类:处理实际逻辑,委托的核心
    // ------------------------------
    private static class MyObjectBuilderImpl implements ValueStep, AnotherValueStep, FieldStep {
        private BuildStep currentStep = BuildStep.NEED_VALUE;
        private String value;
        private String anotherValue;
        private Map<String, String> fields = new HashMap<>();

        @Override
        public AnotherValueStep withValue(String value) {
            validateStep(BuildStep.NEED_VALUE);
            this.value = value;
            currentStep = BuildStep.NEED_ANOTHER_VALUE;
            return this; // 返回当前实例,但类型是AnotherValueStep,只能调用该接口的方法
        }

        @Override
        public FieldStep withAnotherValue(String anotherValue) {
            validateStep(BuildStep.NEED_ANOTHER_VALUE);
            this.anotherValue = anotherValue;
            currentStep = BuildStep.CAN_ADD_FIELDS;
            return this;
        }

        @Override
        public FieldStep withField(String key, String value) {
            validateStep(BuildStep.CAN_ADD_FIELDS);
            fields.put(key, value);
            return this; // 保持FieldStep类型,支持多次调用
        }

        @Override
        public MyObject build() {
            validateStep(BuildStep.CAN_ADD_FIELDS);
            return new MyObject(this);
        }

        // 辅助方法:校验当前阶段是否正确,不正确则抛异常
        private void validateStep(BuildStep expectedStep) {
            if (currentStep != expectedStep) {
                throw new IllegalStateException(
                    String.format("当前阶段不允许该操作!预期阶段:%s,实际阶段:%s", expectedStep, currentStep)
                );
            }
        }
    }
}

使用示例

完全符合你想要的调用方式,而且顺序不对的话要么编译报错,要么运行时抛异常(不过编译期就能拦住大部分错误):

// 正常调用:必填方法按顺序,可选方法可多次调用
MyObject obj1 = MyObject.builder()
    .withValue("test string")
    .withAnotherValue("another test")
    .withField("user", "Alice")
    .withField("age", "25")
    .build();

// 也可以不调用可选方法
MyObject obj2 = MyObject.builder()
    .withValue("hello")
    .withAnotherValue("world")
    .build();

// 错误调用:编译期直接报错!因为builder()返回的是ValueStep,没有withAnotherValue方法
// MyObject wrongObj = MyObject.builder()
//     .withAnotherValue("跳过value直接调用")
//     .build();

为什么这种实现这么靠谱?

  • 编译期强制顺序:通过阶段接口的类型限制,用户根本没法写出跳步的代码,比运行时检查友好太多。
  • 灵活的可选方法:withField()返回的是FieldStep本身,所以可以链式调用N次,完全满足需求。
  • 扩展性强:以后要是需要加新的必填步骤,只需要新增枚举值和对应的阶段接口,修改BuilderImpl的状态切换逻辑就行,改动很小。
  • 职责清晰:枚举管状态,接口管暴露的方法,BuilderImpl管实际逻辑,分工明确,代码可读性高。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 07:54:12