Builder with conditional inclusion of element

21,262

Solution 1

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());

Solution 2

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;
}

Solution 3

I tend to use ternary operators to continue the chain.

So let's say I have your case above:

Builder builder = Builder
    .name("name")
    .id("id");
builder = (complexCondition ? builder.age(age) : builder)
    .occupation("brick-layer")
    .disposition("angry");

That way, you can insert optional items in the chain without completely breaking the flow of it. If it's long, obviously format it on multiple lines.

Another thing you could do is handle null in your age method, so like this:

Builder builder = Builder
    .name("name")
    .id("id")
    .age(complexCondition ? age : null);

Of course, internally, your age method looks something like this:

public Builder age(Integer age) {
    if (age != null) {
        this.age = age;
    }
    return this;
}
Share:
21,262

Related videos on Youtube

Andrii Plotnikov
Author by

Andrii Plotnikov

Updated on July 09, 2022

Comments

  • Andrii Plotnikov
    Andrii Plotnikov almost 2 years

    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

    • JB Nizet
      JB Nizet over 6 years
      Your second snippet looks perfectly fine to me. What don't you like about it? The job of the builder is to accumulate the parts and build the final objects. Not to evaluate conditions, and not to be able to do anything you want in one line.
    • Andrii Plotnikov
      Andrii Plotnikov over 6 years
      they both ARE valid. The point is first - not the job of builder, second - splits builder patter into +3 lines per condition. it's ok when I have one, but I have 10+ params, 5+ conditions and it becomes messy at that point.
    • JB Nizet
      JB Nizet over 6 years
      Well, if you have 10 conditions, you need to write the code of the 10 conditions, whatever the solution is. And trying to pass everything in one line will make that even messier. If you really want to keep the one-liner, you can always use something like int age = defaultAgeValue; if (complexCondition) { age = somethingElse; } and then use the builder. And of course the computation of the age can be extracted to a method. So it can become Builder.name("name").id("id").age(computeAge()).build().
    • Andrii Plotnikov
      Andrii Plotnikov over 6 years
      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 call that calls builder
    • JB Nizet
      JB Nizet over 6 years
      Well, doesn't Builder.name("name").id("id").age(computeAge()).build() fit all these requirements?
    • Andrii Plotnikov
      Andrii Plotnikov over 6 years
      wait, it does.. I didn't notice that if you pass null it won't change result for builder when .build() is called; post this as answer so I can accept it plz :)
  • Andrii Plotnikov
    Andrii Plotnikov over 6 years
    the point is - what if I do NOT have condition. there are 2 possibilities I either have condition or I don't. Your proposition about spliting condition and parameter is better for that - but I'm not so sure it's job of builder to evaluate this. At this point I'm confused, thus I ask the question. I don't see simple solution and there are traid-ofs for each of them.
  • Eran
    Eran over 6 years
    @Sarief If the condition is optional (i.e. only evaluated when passed to the builder), you can make a simple change in my suggested build() method to account for that. I'll edit.
  • Andrii Plotnikov
    Andrii Plotnikov over 6 years
    condition is evaluated before calling builder, i.e. boolean result is already calculated
  • Eran
    Eran over 6 years
    @Sarief Using a Predicate was suggested as an optimization, since I assumed your condition is complex (based on the name you gave it). I thought it might be more efficient to only evaluate the condition when necessary.
  • Andrii Plotnikov
    Andrii Plotnikov over 6 years
    in that case of course it would be, it's just that my case is different :)
  • Andrii Plotnikov
    Andrii Plotnikov over 6 years
    yes, that's what I looked for :D I'm sometimes confused with builder since passing null to method actually won't change result. Thanks!
  • Andrii Plotnikov
    Andrii Plotnikov about 5 years
    nope, builder is not "immutable". It also does not create new instance every time it adds variable or returns itself. If it causes trouble like that, it means someone screwed up implementation. The point of builder is to reduce number of objects created when working with setting up/changing immutable object. Added minus to answer, will remove if proven wrong. Otherwise, please retract the answer
  • Gaurav
    Gaurav over 2 years
    flawless answer @JB Nizet