通过在编译时进行验证来改进构建器模式

joh*_*ohn 4 java design-patterns builder builder-pattern

我最近开始在我的一个项目中使用 Builder 模式,并且尝试在我的 Builder 类上添加某种验证。我假设我们无法在编译时执行此操作,因此这就是我在运行时执行此验证的原因。但也许我错了,这就是我试图看看是否可以在编译时做到这一点。

传统建造者模式

public final class RequestKey {

    private final Long userid;
    private final String deviceid;
    private final String flowid;
    private final int clientid;
    private final long timeout;
    private final boolean abcFlag;
    private final boolean defFlag;
    private final Map<String, String> baseMap;

    private RequestKey(Builder builder) {
        this.userid = builder.userid;
        this.deviceid = builder.deviceid;
        this.flowid = builder.flowid;
        this.clientid = builder.clientid;
        this.abcFlag = builder.abcFlag;
        this.defFlag = builder.defFlag;
        this.baseMap = builder.baseMap.build();
        this.timeout = builder.timeout;
    }

    public static class Builder {
        protected final int clientid;
        protected Long userid = null;
        protected String deviceid = null;
        protected String flowid = null;
        protected long timeout = 200L;
        protected boolean abcFlag = false;
        protected boolean defFlag = true;
        protected ImmutableMap.Builder<String, String> baseMap = ImmutableMap.builder();

        public Builder(int clientid) {
            checkArgument(clientid > 0, "clientid must not be negative or zero");
            this.clientid = clientid;
        }

        public Builder setUserId(long userid) {
            checkArgument(userid > 0, "userid must not be negative or zero");
            this.userid = Long.valueOf(userid);
            return this;
        }

        public Builder setDeviceId(String deviceid) {
            checkNotNull(deviceid, "deviceid cannot be null");
            checkArgument(deviceid.length() > 0, "deviceid can't be an empty string");
            this.deviceid = deviceid;
            return this;
        }

        public Builder setFlowId(String flowid) {
            checkNotNull(flowid, "flowid cannot be null");
            checkArgument(flowid.length() > 0, "flowid can't be an empty string");
            this.flowid = flowid;
            return this;
        }

        public Builder baseMap(Map<String, String> baseMap) {
            checkNotNull(baseMap, "baseMap cannot be null");
            this.baseMap.putAll(baseMap);
            return this;
        }

        public Builder abcFlag(boolean abcFlag) {
            this.abcFlag = abcFlag;
            return this;
        }

        public Builder defFlag(boolean defFlag) {
            this.defFlag = defFlag;
            return this;
        }

        public Builder addTimeout(long timeout) {
            checkArgument(timeout > 0, "timeout must not be negative or zero");
            this.timeout = timeout;
            return this;
        }

        public RequestKey build() {
            if (!this.isValid()) {
                throw new IllegalStateException("You have to pass at least one"
                        + " of the following: userid, flowid or deviceid");
            }
            return new RequestKey(this);
        }

        private boolean isValid() {
            return !(TestUtils.isEmpty(userid) && TestUtils.isEmpty(flowid) && TestUtils.isEmpty(deviceid));
        }
    }

    // getters here
}
Run Code Online (Sandbox Code Playgroud)

问题陈述:

正如您所看到的,我有各种参数,但只有一个参数clientId是强制性的,其余参数是可选的。在我上面的代码中,我需要有userid,flowiddeviceidset。如果这三个都没有设置,那么我将抛出IllegalStateException一条错误消息,如上面的代码所示。如果设置了所有三个或两个,那么就可以了,我正在对这三个或两个进行一些优先级逻辑来决定使用哪一个,但至少必须设置其中一个。

并不强制他们每次都传递所有三个 id,他们可以传递所有三个,有时两个或有时仅一个,但条件是应设置其中之一。

我正在寻找的是 - 我可以在编译时执行此操作并且不构建我的构建器模式,除非设置了这三个中的任何一个并且在编译时它应该告诉缺少什么,而不是在运行时执行所有这些操作?

我发现这个 SO链接完全讨论了同样的事情,但不确定如何在我的场景中使用它?还有这个带有扭曲的构建器模式和这个SO问题

hot*_*zst 5

根据您的描述,我会采用您已经提到的解决方案: 如何改进构建器模式?和略有变化的构建器模式:

这两种解决方案基本上都是链式构建器:您调用Builder.create().firstMandatoryField()它将返回第二个必填字段的构建器实例,依此类推,直到到达最后一个构建器,该构建器具有build调用私有构造函数的实际方法。

正如您的情况一样,有一些字段至少必须设置其中一个,这意味着您的第一个构建器将提供方法来初始化它们并返回第二个构建器的实例。然后,您可以在第二个构建器上设置所有字段(可选字段和必填字段)。

这是实现这一目标的一个版本:

public final class RequestKey {

    private final Long userid;
    private final String deviceid;
    private final String flowid;
    private final int clientid;
    private final long timeout;
    private final boolean abcFlag;
    private final boolean defFlag;
    private final Map<String, String> baseMap;

    private RequestKey(FinalBuilder builder) {
        this.userid = builder.userid;
        this.deviceid = builder.deviceid;
        this.flowid = builder.flowid;
        this.clientid = builder.clientid;
        this.abcFlag = builder.abcFlag;
        this.defFlag = builder.defFlag;
        this.baseMap = builder.baseMap.build();
        this.timeout = builder.timeout;
    }
    public static class Builder {
        public Builder1 clientId(int clientid) {
            checkArgument(clientid > 0, "clientid must not be negative or zero");
            return new Builder1(clientid);
        }
    }
    public static class Builder1 {
        private final int clientid;

        Builder1(int clientid){
            this.clientid = clientid;
        }
        public FinalBuilder userId(long userid) {
            checkArgument(userid > 0, "userid must not be negative or zero");
            FinalBuilder builder = new FinalBuilder(clientid);
            return builder.setUserId(userid);
        }

        public FinalBuilder deviceId(String deviceid) {
            checkNotNull(deviceid, "deviceid cannot be null");
            checkArgument(deviceid.length() > 0, "deviceid can't be an empty string");
            FinalBuilder builder = new FinalBuilder(clientid);
            return builder.setDeviceId(deviceid);
        }

        public FinalBuilder flowId(String flowid) {
            checkNotNull(flowid, "flowid cannot be null");
            checkArgument(flowid.length() > 0, "flowid can't be an empty string");
            FinalBuilder builder = new FinalBuilder(clientid);
            return builder.setFlowId(flowid);
        }
    }

    public static class FinalBuilder {
        private final int clientid;
        private Long userid = null;
        private String deviceid = null;
        private String flowid = null;
        private long timeout = 200L;
        private boolean abcFlag = false;
        private boolean defFlag = true;
        private ImmutableMap.Builder<String, String> baseMap = ImmutableMap.builder();

        FinalBuilder(int clientId) {
            this.clientid = clientId;
        }


        FinalBuilder setUserId(long userid) {
            this.userid = userid;
            return this;
        }

        FinalBuilder setDeviceId(String deviceid) {
            this.deviceid = deviceid;
            return this;
        }

        FinalBuilder setFlowId(String flowid) {
            this.flowid = flowid;
            return this;
        }
        public FinalBuilder userId(long userid) {
            checkArgument(userid > 0, "userid must not be negative or zero");
            this.userid = Long.valueOf(userid);
            this.userid = userid;
            return this;
        }

        public FinalBuilder deviceId(String deviceid) {
            checkNotNull(deviceid, "deviceid cannot be null");
            checkArgument(deviceid.length() > 0, "deviceid can't be an empty string");
            this.deviceid = deviceid;
            return this;
        }

        public FinalBuilder flowId(String flowid) {
            checkNotNull(flowid, "flowid cannot be null");
            checkArgument(flowid.length() > 0, "flowid can't be an empty string");
            this.flowid = flowid;
            return this;
        }

        public FinalBuilder baseMap(Map<String, String> baseMap) {
            checkNotNull(baseMap, "baseMap cannot be null");
            this.baseMap.putAll(baseMap);
            return this;
        }

        public FinalBuilder abcFlag(boolean abcFlag) {
            this.abcFlag = abcFlag;
            return this;
        }

        public FinalBuilder defFlag(boolean defFlag) {
            this.defFlag = defFlag;
            return this;
        }

        public FinalBuilder addTimeout(long timeout) {
            checkArgument(timeout > 0, "timeout must not be negative or zero");
            this.timeout = timeout;
            return this;
        }

        public RequestKey build() {
            return new RequestKey(this);
        }

    }
    public static Builder create() {
        return new Builder();
    }


    // getters here
}
Run Code Online (Sandbox Code Playgroud)

然后你可以用以下方式调用它:

RequestKey.create()
    .clientId(1234) // Builder of the first level for the mandatory field
    .userId(549375349) // Builder of the second level for any of the additional three mandatory fields
    .flowId("flow number") // Builder on the last level allows setting and overriding the three additional mandatory fields
    .timeout(3600*1000) // Builder on the last level allows setting of the optional fields
    .build(); // Create the instance
Run Code Online (Sandbox Code Playgroud)