vendredi 25 décembre 2015

Improving builder pattern by doing validations at compile time

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

Aucun commentaire:

Enregistrer un commentaire