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 actuallybuild
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:
Then you can call it with: