I'm a member of Transylvania JUG, a community from Cluj-Napoca/Romania interested in exchange of Java knowledge between members and keeping in touch with the latest Java technologies, frameworks and development trends. Attila-mihaly is a DZone MVB and is not an employee of DZone and has posted 19 posts at DZone. You can read more from them at their website. View Full User Profile

Nested Fluent Builders

06.25.2013
| 3333 views |
  • submit to reddit

Builders have become commonplace in current Java code. They have the effect of transforming the following code:

new Foo(1, 5, "abc", false);

Into something like

Foo.builder()
  .count(1)
  .priority(5)
  .name("abc")
  .canonical(true)
  .build();

This has the advantage of being much easier to understand (as a downside we can mention the fact that – depending on the implementation – it can result in the creation of an additional object). The implementation of such builders is very simple – they a list of “setters” which return the current object:

public final class FooBuilder {
  private int count = 1;
  // ...
 
  public FooBuilder count(int count) {
    this.count = count;
    return this; 
  }
 
  public Foo build() {
    return new Foo(count, //...
  }
}

Of course writing even this code can become repetitive and annoying, in which case we can use Lombok or other code generation tools. An other possible improvement – which makes builder more useful for testing – is to add methods like random as suggested in this Java Advent Calendar article. We can subclass the builder (into FooTestBuilder for example) and only use the “extended” version in testing.

What can do however if our objects are more complex (they have non-primitive fields)? One approach may look like this:

Foo.builder()
  .a(1)
  .b(2)
  .bar(Bar.builder().c(1).build())
  .buzz(Buzz.builder().build())
  .build();

We can make this a little nicer by overloading the bar / buzz methods to accept instances of BarBuilder / BuzzBuilder, in which case we can omit two build calls. Still, I longed for something like the following:

Foo.builder()
  .a(1)
  .b(2)
  .bar()
     .c(1).build()
  .buzz()
     .build()
  .build();

The idea is that the bar / buzz calls call start a new “context” where we initialize the Bar/Buzz classes. “build” calls end the innermost context, with the last build returning the initialized Foo object itself. How can this be written in a typesafe / compiler verifiable way?

My solution is the following:

  • Each builder is parameterized to return an arbitrary type T from its build method
  • The actual return value is generated from a Sink of T
  • When using the builder at the top level, we use an IdentitySink with just returns the passed in value.
  • When using the builder in a nested context, we use a Sink which stores the value and returns the builder from “one level up”.

Some example code to clarify the explanation from above can be found below. Note that this code has been written as an example and could be optimized (like making using a single instance of the IdentitySink, having FooBuilder itself implementing the sink methods, etc).

Implementation of a leaf-level builder:

interface Sink<T> {
  T setBar(Bar bar);
}
 
final class Bar {
  // ...
  public static BarBuilder<Bar> builder() {
    return new BarBuilder<Bar>(new Sink<Bar>() {
      @Override
      public Bar setBar(Bar bar) { return bar; }
    });
  }
}
 
class BarBuilder<T> {
  // ...
 
  protected BarBuilder(Sink<T> sink) {
    this.sink = sink;
  }
 
  // ...
 
  public T build() {
    return sink.setBar(new Bar(c, d, fizz));
  }
}

Implementation of the root level builder: 

class FooBuilder {
  // ...
  public BarBuilder<FooBuilder> setBar() {
    return new BarBuilder(new Sink<FooBuilder>() {
      @Override
      public Bar setBar(Bar bar) { 
        FooBuilder.this.bar = bar;
        return FooBuilder.this;
      }
    });
  }
 
  // ...
}

Conclusion: Java has some missing features (liked named parameters or the ease of reuse provided by duck-typing). We can work around them however nicely with some carefully crafted code (and we can put repeating code into code generators to avoid having to write it over and over again). In exchange we get a very versatile and good performing cross-platform runtime.

Published at DZone with permission of Attila-mihaly Balazs, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Nicolas Frankel replied on Tue, 2013/06/25 - 3:56am

Builders are very good from a "user" point of view. But if you are the API developer, things tend to get complicated.

Builders are to create objects: but then, the build() method that returns the final object should only be called in a stable state. Moreover, method chaining isn't always idempotent, meaning method calls may have to be ordered. Therefore, as your article describes, you need intermediate classes, and may be in numbers. At this point, I can only give your praises: it tries once with only a couple of limited cases, and that was complex.

Scala is much more adapted to those complex cases, with dynamic types (A with B).

Marek Dec replied on Tue, 2013/06/25 - 7:14pm

 @Nikolas,

A good API is an API convenient for the client and not the "developer".

And when it comes to ordering the calls, you may simplify it a little bit employing Java interfaces in a way that your builder class implements all of the interfaces, but the builder methods return only the interfaces that contain allowed methods.

On the first example given here: if you want to enforce setting 'canonical' only once 'name' is set, the 'name(...)' method should return an interface that contains the CanonicalSetter interface. In turn CanonicalSetter should declare the canonical(boolean) method and it should be implemented by the builder.

Natan Cox replied on Wed, 2013/06/26 - 12:49pm

Or you could stop using builders for trivial cases... no need to code builders then.

Foo foo = new Foo() {{
  setA(1);
  setB(5);
  setBar(new Bar() {{
    setC("c");
  }});  
}}

Attila-mihaly Balazs replied on Thu, 2013/06/27 - 1:24am in response to: Natan Cox

Agreed, the examples presented in the article are somewhat trivial for the sake of making the code shorter and wouldn't warrant the use of builders.

As soon as you have 5+ parameters however they become very helpful (mainly because Java doesn't have named parameters with default values). Also, builders can check invariants on the data (like A has to be between 1 and 5, or even cross paramaters like A must be less than B).

Finally, writing builders can be annoying, but using something like Project Lombok (http://projectlombok.org/features/experimental/Builder.html) this can be simplified considerably.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.