Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Builder with conditional inclusion of element

Tags:

I've been wondering is possible to do Builder with optional parameters more elegantly:

what I have: Object with name, id, age.

I have complex condition for inclusion of age and I'd like to send it to builder on success of that condition but i'd like to have it elegant one liner with one parameter.

what i have so far:

 Builder.name("name").id("id").age(age, complexCondition).build();

or

 Builder builder = Builder.name("name").id("id");
 if(complexCondition){
     builder.age(age);
 }

are there any better options? I'd like to resolve condition where I have it without over-engineering builder and without overcoding for every complex condition check.

upd: what I'm looking for is solution without:

a) passing complexChecks or booleans to builder -- not his job to check by definition

b) without adding 3 lines per condition check inside method that calls builder

like image 259
Andrii Plotnikov Avatar asked Jan 21 '18 15:01

Andrii Plotnikov


2 Answers

My answer would be to keep it simple. The responsibility of a builder is to build an object. Not to provide a complex DSL to evaluate conditions. So your second snippet is perfectly fine.

All you need, to avoid overloading the code with many if checks interlaced with calls to the builder is to extract the code of these checks to methods. So you can go from

Builder builder = Builder.name("name").id("id");
if (complexCondition) {
    builder.age(age);
}

to

Integer age = null; // or whatever other default value you want
if (complexCondition) {
    age = somethingElse;
}
Builder builder = Builder.name("name").id("id").age(age);

and finally, bu extracting the 4 first lines to a method computing and returning the age, to

Builder builder = Builder.name("name").id("id").age(computeAge());

I personally prefer it indented the following way, which, IMO, makes it more readable and easier to debug:

Builder builder = Builder.name("name")
                         .id("id")
                         .age(computeAge());
like image 131
JB Nizet Avatar answered Sep 23 '22 14:09

JB Nizet


Well, if you want one argument per method call, you can split

Builder.name("name").id("id").age(age, complexCondition).build();

into

Builder.name("name").id("id").age(age).ageCondition(complexCondition).build();

You might want to consider making complexCondition a Predicate<Something> (where Something is an instance of some type that is used to evaluate the condition). This way, when you call the Builder's build(), you only evaluate the complex condition if the age parameter was supplied.

The build method can look like this:

public SomeClass build() {
    SomeClass obj = new SomeClass();
    obj.setName(name);
    if (age != null && someCondition != null && someCondition.test(something)) {
        obj.setAge(age);
    }
    return obj;
}

I'm not sure what something would be. That depends on the nature of your complex condition.

Or, if you wish the condition to be optional:

public SomeClass build() {
    SomeClass obj = new SomeClass();
    obj.setName(name);
    if (age != null) {
        if (someCondition != null)) {
            if (someCondition.test(something)) {
                obj.setAge(age);
            }
        } else {
            obj.setAge(age);
        }
    }
    return obj;
}
like image 2
Eran Avatar answered Sep 23 '22 14:09

Eran