通过在编译时进行验证来改进构建器模式 [英] Improving builder pattern by doing validations at compile time

查看:729
本文介绍了通过在编译时进行验证来改进构建器模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近在我的一个项目中开始使用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不能为负数或零);
this.clientid = clientid;
}

public Builder setUserId(long userid){
checkArgument(userid> 0,userid不能为负数或零);
this.userid = Long.valueOf(userid);
返回这个;
}

public Builder setDeviceId(String deviceid){
checkNotNull(deviceid,deviceid不能为空);
checkArgument(deviceid.length()> 0,deviceid不能为空字符串);
this.deviceid = deviceid;
返回这个;
}

public Builder setFlowId(String flowid){
checkNotNull(flowid,flowid can not be null);
checkArgument(flowid.length()> 0,flowid不能为空字符串);
this.flowid = flowid
返回这个;
}

public Builder baseMap(Map< String,String> baseMap){
checkNotNull(baseMap,baseMap can not be null);
this.baseMap.putAll(baseMap);
返回这个;
}

public Builder abcFlag(boolean abcFlag){
this.abcFlag = abcFlag;
返回这个;
}

public Builder defFlag(boolean defFlag){
this.defFlag = defFlag;
返回这个;
}

public Builder addTimeout(long timeout){
checkArgument(timeout> 0,timeout不能为负数或零);
this.timeout = timeout;
返回这个;
}

public RequestKey build(){
if(!this.isValid()){
throw new IllegalStateException(你必须传递至少一个
+:userid,flowid或deviceid);
}
返回新的RequestKey(this);
}

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

// getter here
}

问题陈述:



如您所见,我有各种参数,但只有一个参数 clientId 是强制性的,其余的是可选的。在我的上述代码中,我需要使用 userid flowid deviceid 设置。如果没有这三个设置,那么我抛出 IllegalStateException ,并显示如上所示的代码中的错误消息。如果所有三个或两个被设置好,那么这是很好的,我在这三到两个决定使用哪一个优先级逻辑,但至少其中一个必须设置。



我正在寻找的是 - 而不是在运行时做所有这些事情,我可以在编译时这样做,不建立我的构建器模式,除非这三个设置在编译时应该告诉你什么缺失?



我发现这个SO 链接,它完全谈论同样的事情,但不知道如何在我的场景中使用它?此外,具有扭曲的构建器模式而这个 SO问题

解决方案

根据您的描述,我将与您已经提到的解决方案一起使用:
如何改进建设者模式?具有轻微变化的生成器模式



这两个解决方案基本上链接构建器又一个:您调用 Builder.create()。firstMandatoryField()将返回第二个必填字段的构建器实例,直到达到最后一个生成器,其实际上是构建方法调用私有构造函数。



在您的情况下,有一些字段必须至少设置一个,这意味着您的第一个构建器将提供方法初始化它们并返回第二个构建器的实例。在第二个构建器上,您可以设置所有字段(可选项以及必选项)。



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

  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不能为负数或零);
返回新的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不能为负数或零);
FinalBuilder builder = new FinalBuilder(clientid);
return builder.setUserId(userid);
}

public FinalBuilder deviceId(String deviceid){
checkNotNull(deviceid,deviceid not not null);
checkArgument(deviceid.length()> 0,deviceid不能为空字符串);
FinalBuilder builder = new FinalBuilder(clientid);
return builder.setDeviceId(deviceid);
}

public FinalBuilder flowId(String flowid){
checkNotNull(flowid,flowid can not be null);
checkArgument(flowid.length()> 0,flowid不能为空字符串);
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;
返回这个;
}

FinalBuilder setDeviceId(String deviceid){
this.deviceid = deviceid;
返回这个;
}

FinalBuilder setFlowId(String flowid){
this.flowid = flowid;
返回这个;
}
public FinalBuilder userId(long userid){
checkArgument(userid> 0,userid不能为负数或零);
this.userid = Long.valueOf(userid);
this.userid = userid;
返回这个;
}

public FinalBuilder deviceId(String deviceid){
checkNotNull(deviceid,deviceid not not null);
checkArgument(deviceid.length()> 0,deviceid不能为空字符串);
this.deviceid = deviceid;
返回这个;
}

public FinalBuilder flowId(String flowid){
checkNotNull(flowid,flowid can not be null);
checkArgument(flowid.length()> 0,flowid不能为空字符串);
this.flowid = flowid;
返回这个;
}

public FinalBuilder baseMap(Map< String,String> baseMap){
checkNotNull(baseMap,baseMap can not be null);
this.baseMap.putAll(baseMap);
返回这个;
}

public FinalBuilder abcFlag(boolean abcFlag){
this.abcFlag = abcFlag;
返回这个;
}

public FinalBuilder defFlag(boolean defFlag){
this.defFlag = defFlag;
返回这个;
}

public FinalBuilder addTimeout(long timeout){
checkArgument(timeout> 0,timeout不能为负数或零);
this.timeout = timeout;
返回这个;
}

public RequestKey build(){
返回新的RequestKey(this);
}

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


// getter here
}

然后可以使用以下命令:

  RequestKey.create()
.clientId(1234) //必需字段的第一级生成器
.userId(549375349)//任何其他三个必填字段的第二级生成器
.flowId(flow number)// Builder在最后一级允许设置和覆盖三个额外的必填字段
.timeout(3600 * 1000)//上一级的Builder允许设置可选字段
.build(); //创建实例


I recently started using Builder pattern in one of my projects and I am trying to add some sort of validations on my Builder class. I am assuming we cannot do this at compile time so that's why I am doing this validation at runtime. But may be I am wrong and that's what I am trying to see whether I can do this at compile time.

Traditional builder pattern

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
}

Problem Statement:

As you can see I have various parameters but only one parameter clientId is mandatory and rest of them are optional. In my above code, I need to have either userid, flowid or deviceid set. If none of those three is set then I am throwing IllegalStateException with an error message as shown above in the code. If all three or two is set then it's fine and I am doing some priority logic on those three or two to decide which one to use but atleast one of them has to be set.

It is not mandatory that they will pass all three id's everytime, they can pass all three or sometimes two or sometimes only one but the condition is either one of them should be set.

What I am looking for is - Instead of doing all these things at runtime, can I do this at compile time and don't build my builder pattern unless either of these three is set and at compile time it should tell what is missing?

I found out this SO link which exactly talks about same thing but not sure how can I use it in my scenario? And also this builder pattern with a twist and this SO question

解决方案

From your description I would go with the solutions you already mentioned: How to improve the builder pattern? and Builder pattern with a twist with a slight variation:

Both these solutions basically chain builder after another: You call Builder.create().firstMandatoryField() which will return an instance of the builder for the second mandatory field and so on until you reach the last builder, which has the actually build method which calls the private constructor.

As in your case there are some fields where at least one of them must be set, that would mean, that your first builder would provide methods to initialize them and return an instance of the second builder. On the second builder you then can set all fields (the optionals as well as the mandatory ones).

This is one version to achieve this:

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
}

Then you can call it with:

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

这篇关于通过在编译时进行验证来改进构建器模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆