patternjavaMinor
Validating multiple keys in the builder class based on the rules
Viewed 0 times
thebuildervalidatingkeysmultiplebasedrulesclass
Problem
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 have already asked a question, got some feedback and incorporated it into my code.
Below is my builder class -
```
public final class DataKey {
private final long userId;
private final String uuid;
private final String deviceId;
private final int clientId;
private final FlowEnum flow;
private DataKey(Builder builder) {
this.userId = builder.userId;
this.uuid = builder.uuid;
this.deviceId = builder.deviceId;
this.clientId = builder.clientId;
this.flow = (userId == 0) ? FlowEnum.DEVICEFLOW : FlowEnum.USERFLOW;
}
public static class Builder {
protected final int clientId;
protected long userId;
protected String uuid;
protected String deviceId;
protected boolean userIdFlag = false;
protected boolean uuidFlag = false;
protected boolean deviceIdFlag = false;
public Builder(int clientId) {
this.clientId = clientId;
}
public Builder setUserId(long userId) {
this.userId = userId;
this.userIdFlag = true;
return this;
}
public Builder setUuid(String uuid) {
this.uuid = uuid;
this.uuidFlag = true;
return this;
}
public Builder setDeviceId(String deviceId) {
this.deviceId = deviceId;
this.deviceIdFlag = true;
return this;
}
public DataKey build(){
if (userIdFlag) {
if (userId <= 0) {
throw new IllegalArgumentException(" USERID cannot be negative or zero");
}
} else if (uuidFlag && !userIdFlag) {
if (uuid == null || uuid.isEmpty()) {
throw new IllegalArgumentException("UUID cannot be null OR empty");
}
Below is my builder class -
```
public final class DataKey {
private final long userId;
private final String uuid;
private final String deviceId;
private final int clientId;
private final FlowEnum flow;
private DataKey(Builder builder) {
this.userId = builder.userId;
this.uuid = builder.uuid;
this.deviceId = builder.deviceId;
this.clientId = builder.clientId;
this.flow = (userId == 0) ? FlowEnum.DEVICEFLOW : FlowEnum.USERFLOW;
}
public static class Builder {
protected final int clientId;
protected long userId;
protected String uuid;
protected String deviceId;
protected boolean userIdFlag = false;
protected boolean uuidFlag = false;
protected boolean deviceIdFlag = false;
public Builder(int clientId) {
this.clientId = clientId;
}
public Builder setUserId(long userId) {
this.userId = userId;
this.userIdFlag = true;
return this;
}
public Builder setUuid(String uuid) {
this.uuid = uuid;
this.uuidFlag = true;
return this;
}
public Builder setDeviceId(String deviceId) {
this.deviceId = deviceId;
this.deviceIdFlag = true;
return this;
}
public DataKey build(){
if (userIdFlag) {
if (userId <= 0) {
throw new IllegalArgumentException(" USERID cannot be negative or zero");
}
} else if (uuidFlag && !userIdFlag) {
if (uuid == null || uuid.isEmpty()) {
throw new IllegalArgumentException("UUID cannot be null OR empty");
}
Solution
Flags and Flag-flags:
Everytime your Variable name includes flag, there should be a flag raised in your head. You then should go back and either extract a method or a class.
Usually a flag means you are trying to do two different things in one method and that's something you shouldn't. Instead you should extract a method for each separate behavior.
Instead of these boolean flags you should initialize them to
In your validation you can then just check for null or empty / 0 and throw an IllegalArgumentException in case that happens. then you don't even need that whole if-else swapping.
Repeatedly throwing exceptions:
You repeat your throwing of
What you are really doing in your validation:
Some pseudocode to show what exactly you are doing:
Usually it is recommended to fail as early as possible. This means, you shouldn't even allow setting of null (or empty / <= 0) values to
Accordingly you should change your setters to:
and then your build-method becomes:
Everytime your Variable name includes flag, there should be a flag raised in your head. You then should go back and either extract a method or a class.
Usually a flag means you are trying to do two different things in one method and that's something you shouldn't. Instead you should extract a method for each separate behavior.
Instead of these boolean flags you should initialize them to
null and just check if they are set when you validate.//note that I changed the type of userId to the wrapper class Long
protected Long userId = null;
protected String uuId = null;
protected String deviceId = null;
protected final int clientId;In your validation you can then just check for null or empty / 0 and throw an IllegalArgumentException in case that happens. then you don't even need that whole if-else swapping.
Repeatedly throwing exceptions:
You repeat your throwing of
IllegalArgumentException in each and every block you have defined in your if-statements. Instead you should extract your throwing of IllegalArgumentException into a helper method:private boolean throwOnNullOrEmpty(String value, String name) {
if(value == null || value.isEmpty()) {
throw new IllegalArgumentException(name + " may not be NULL or EMPTY");
}
return true;
}What you are really doing in your validation:
Some pseudocode to show what exactly you are doing:
IF userId is set
VALIDATE userId (input)
ELSE IF uuid is set
VALIDATE uuId (input)
ELSE IF deviceId is set
VALIDATE deviceId (input)
ELSE
THROW IllegalStateExceptionUsually it is recommended to fail as early as possible. This means, you shouldn't even allow setting of null (or empty / <= 0) values to
userId, uuid and deviceId. You can then proceed to drop about all of your validation except:IF userId not set AND uuId not set AND deviceid not set
THROW IllegalStateExceptionAccordingly you should change your setters to:
public Builder setUserId(long userId) {
if(userId <= 0) {
throw new IllegalArgumentException("userId may not be zero or less");
}
this.userId = new Long(userid);
return this;
}
public Builder setUuid(String uuId) {
throwOnNullOrEmpty(uuid, "UUID");
this.uuId = uuId;
return this;
}
public Builder setDeviceId(String deviceId) {
throwOnNullOrEmpty(deviceId, "DEVICE ID");
this.deviceId = deviceId;
return this;
}and then your build-method becomes:
public DataKey build() {
if(userId == null && uuId == null && deviceId == null) {
throw new IllegalStateException("You need to set at least one of userId, uuid or deviceId");
}
return new DataKey(this);
}Code Snippets
//note that I changed the type of userId to the wrapper class Long
protected Long userId = null;
protected String uuId = null;
protected String deviceId = null;
protected final int clientId;private boolean throwOnNullOrEmpty(String value, String name) {
if(value == null || value.isEmpty()) {
throw new IllegalArgumentException(name + " may not be NULL or EMPTY");
}
return true;
}IF userId is set
VALIDATE userId (input)
ELSE IF uuid is set
VALIDATE uuId (input)
ELSE IF deviceId is set
VALIDATE deviceId (input)
ELSE
THROW IllegalStateExceptionIF userId not set AND uuId not set AND deviceid not set
THROW IllegalStateExceptionpublic Builder setUserId(long userId) {
if(userId <= 0) {
throw new IllegalArgumentException("userId may not be zero or less");
}
this.userId = new Long(userid);
return this;
}
public Builder setUuid(String uuId) {
throwOnNullOrEmpty(uuid, "UUID");
this.uuId = uuId;
return this;
}
public Builder setDeviceId(String deviceId) {
throwOnNullOrEmpty(deviceId, "DEVICE ID");
this.deviceId = deviceId;
return this;
}Context
StackExchange Code Review Q#55754, answer score: 3
Revisions (0)
No revisions yet.