Builder with conditional inclusion of element
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;
}
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());
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;
}