通过在编译时进行验证来改进构建器模式
Improving builder pattern by doing validations at compile time
我最近开始在我的一个项目中使用 Builder 模式,我正在尝试在我的 Builder class 上添加某种验证。我假设我们不能在编译时执行此操作,所以这就是我在运行时执行此验证的原因。但可能是我错了,这就是我想看看我是否可以在编译时做到这一点。
传统生成器模式
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
}
问题陈述:
如您所见,我有各种参数,但只有一个参数 clientId
是必需的,其余参数都是可选的。在我上面的代码中,我需要设置 userid
、flowid
或 deviceid
。如果设置了这三个中的 none,那么我将抛出 IllegalStateException
并显示一条错误消息,如上面的代码所示。如果所有三个或两个都已设置,那很好,我正在对这三个或两个执行一些优先级逻辑来决定使用哪一个,但至少必须设置其中一个。
他们不是必须每次都传递所有三个 id,他们可以传递所有三个,有时传递两个,有时只传递一个,但条件是应设置其中一个。
我正在寻找的是 - 除了在运行时做所有这些事情,我可以在编译时做这个并且不构建我的构建器模式,除非这三个中的任何一个被设置并且在编译时它应该告诉缺少什么?
我发现了这个 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
根据您的描述,我会采用您已经提到的解决方案:
How to improve the builder pattern? and Builder pattern with a twist 略有不同:
这两种解决方案基本上都是链式构建器:你调用 Builder.create().firstMandatoryField()
它将 return 第二个必填字段的构建器实例,依此类推,直到你到达最后一个构建器,它具有调用私有构造函数的实际 build
方法。
对于您的情况,有些字段必须至少设置其中一个,这意味着您的第一个构建器将提供初始化它们的方法,return 第二个构建器的实例。然后,在第二个构建器上,您可以设置所有字段(可选字段和必填字段)。
这是实现此目的的一个版本:
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
}
然后你可以调用它:
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
我最近开始在我的一个项目中使用 Builder 模式,我正在尝试在我的 Builder class 上添加某种验证。我假设我们不能在编译时执行此操作,所以这就是我在运行时执行此验证的原因。但可能是我错了,这就是我想看看我是否可以在编译时做到这一点。
传统生成器模式
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
}
问题陈述:
如您所见,我有各种参数,但只有一个参数 clientId
是必需的,其余参数都是可选的。在我上面的代码中,我需要设置 userid
、flowid
或 deviceid
。如果设置了这三个中的 none,那么我将抛出 IllegalStateException
并显示一条错误消息,如上面的代码所示。如果所有三个或两个都已设置,那很好,我正在对这三个或两个执行一些优先级逻辑来决定使用哪一个,但至少必须设置其中一个。
他们不是必须每次都传递所有三个 id,他们可以传递所有三个,有时传递两个,有时只传递一个,但条件是应设置其中一个。
我正在寻找的是 - 除了在运行时做所有这些事情,我可以在编译时做这个并且不构建我的构建器模式,除非这三个中的任何一个被设置并且在编译时它应该告诉缺少什么?
我发现了这个 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
根据您的描述,我会采用您已经提到的解决方案: How to improve the builder pattern? and Builder pattern with a twist 略有不同:
这两种解决方案基本上都是链式构建器:你调用 Builder.create().firstMandatoryField()
它将 return 第二个必填字段的构建器实例,依此类推,直到你到达最后一个构建器,它具有调用私有构造函数的实际 build
方法。
对于您的情况,有些字段必须至少设置其中一个,这意味着您的第一个构建器将提供初始化它们的方法,return 第二个构建器的实例。然后,在第二个构建器上,您可以设置所有字段(可选字段和必填字段)。
这是实现此目的的一个版本:
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
}
然后你可以调用它:
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