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

Java 通过在编译时进行验证来改进构建器模式,java,design-patterns,builder,builder-pattern,Java,Design Patterns,Builder,Builder Pattern,我最近开始在我的一个项目中使用Builder模式,我正在尝试在我的Builder类上添加某种验证。我假设我们不能在编译时进行验证,所以我在运行时进行验证。但可能是我错了,这就是我想看看我是否能在编译时做到这一点的原因 传统的构建器模式 public final class RequestKey { private final Long userid; private final String deviceid; private final String flowid;

我最近开始在我的一个项目中使用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
}
公共最终类请求密钥{
私有最终长用户ID;
专用最终字符串设备ID;
私有最终字符串flowid;
私人最终int客户ID;
私有最终长超时;
私有最终布尔abcFlag;
私有最终布尔标志;
私人最终地图底图;
私有请求密钥(生成器){
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;
}
公共静态类生成器{
受保护的最终int客户端ID;
受保护的长userid=null;
受保护的字符串deviceid=null;
受保护的字符串flowid=null;
受保护的长超时=200L;
受保护布尔值abcFlag=false;
受保护的布尔值defFlag=true;
受保护的ImmutableMap.Builder baseMap=ImmutableMap.Builder();
公共生成器(int客户端ID){
checkArgument(clientid>0,“clientid不能为负或零”);
this.clientid=clientid;
}
公共生成器setUserId(长用户id){
checkArgument(userid>0,“userid不能为负或零”);
this.userid=Long.valueOf(userid);
归还这个;
}
公共生成器setDeviceId(字符串deviceid){
checkNotNull(deviceid,“deviceid不能为null”);
checkArgument(deviceid.length()>0,“deviceid不能是空字符串”);
this.deviceid=deviceid;
归还这个;
}
公共生成器setFlowId(字符串flowid){
checkNotNull(flowid,“flowid不能为null”);
checkArgument(flowid.length()>0,“flowid不能是空字符串”);
this.flowid=flowid;
归还这个;
}
公共生成器基本映射(映射基本映射){
checkNotNull(baseMap,“baseMap不能为null”);
this.baseMap.putAll(baseMap);
归还这个;
}
公共生成器abcFlag(布尔abcFlag){
this.abcFlag=abcFlag;
归还这个;
}
公共生成器定义标志(布尔定义标志){
this.defsflag=defsflag;
归还这个;
}
公共生成器addTimeout(长超时){
checkArgument(超时>0,“超时不能为负或零”);
this.timeout=超时;
归还这个;
}
公共请求密钥生成(){
如果(!this.isValid()){
抛出新的非法状态异常(“您必须至少通过一个”
+“以下各项之一:userid、flowid或deviceid”);
}
返回新的RequestKey(这个);
}
私有布尔值isValid(){
return!(TestUtils.isEmpty(userid)和&TestUtils.isEmpty(flowid)和&TestUtils.isEmpty(deviceid));
}
}
//这里有吸气剂
}
问题陈述:

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

并非强制要求他们每次都通过所有三个id,他们可以通过所有三个,有时两个,有时只有一个,但条件是应设置其中一个

我要寻找的是——我可以在编译时做这些事情,而不是在运行时做这些事情,并且不构建构建器模式,除非这三者中的任何一个被设置,并且在编译时它应该告诉我缺少什么


我发现了这一点,所以它完全谈论了相同的事情,但不确定如何在我的场景中使用它?还有这个和这个

根据您的描述,我将使用您已经提到的解决方案: 还有一点变化:

这两种解决方案基本上是一个接一个地链接构建器:调用
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
}

你能举一个例子,在我的例子中它是什么样子的吗?这是我无法理解的。谢谢你的解释。当我尝试您的建议时,它给出了各种字段名的编译错误,因为它无法以某种方式找到这些字段名。事实上,这个模式让我很困惑,这就是为什么我想粘贴它并了解它是如何工作的..修复了编译问题。谢谢修复。有什么方法可以像我现在使用的那样通过m中的构造函数来使用clientId吗
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