All Projects → davidmoten → java-builder-pattern-tricks

davidmoten / java-builder-pattern-tricks

Licence: Apache-2.0 license
Tricks to use with the java builder pattern

Projects that are alternatives of or similar to java-builder-pattern-tricks

csharp-design-patterns-for-humans
Design Patterns for Humans™ - An ultra-simplified explanation - C# Examples
Stars: ✭ 1,086 (+2684.62%)
Mutual labels:  builder-pattern
csharp-design-patterns-for-humans-examples
Complete C# Examples Refereed in csharp-design-patterns-for-humans
Stars: ✭ 50 (+28.21%)
Mutual labels:  builder-pattern
annotation-processor-sample
An annotation processor which implements "Builder pattern" for your java classes.
Stars: ✭ 22 (-43.59%)
Mutual labels:  builder-pattern
BalloonPopup
Forget Android Toast! BalloonPopup displays a round or squared popup and attaches it to a View, like a callout. Uses the Builder pattern for maximum ease. The popup can automatically hide and can persist when the value is updated.
Stars: ✭ 32 (-17.95%)
Mutual labels:  builder-pattern
securitybuilder
Fluent builders with typesafe API for the JCA
Stars: ✭ 42 (+7.69%)
Mutual labels:  builder-pattern
manager
The builder (Manager) pattern implementation used by AdonisJs
Stars: ✭ 14 (-64.1%)
Mutual labels:  builder-pattern
Flapi
Flapi is an API generator for Java, which generates 'smart' interfaces for improved fluency in your code.
Stars: ✭ 56 (+43.59%)
Mutual labels:  builder-pattern
design-patterns-php
All Design Patterns Samples in PHP
Stars: ✭ 43 (+10.26%)
Mutual labels:  builder-pattern
Java-design-patterns
Java Design patterns.
Stars: ✭ 49 (+25.64%)
Mutual labels:  builder-pattern
go-builder-generator-idea-plugin
IntelliJ IDEA / GoLand golang plugin for generating Builder pattern code of golang struct.
Stars: ✭ 21 (-46.15%)
Mutual labels:  builder-pattern
flexiblesearchbuilder
Flexible search query builder is SAP Hybris Commerce extension (released as a library) that provides developer-friendly way to build flexible search queries. The aim of this extension is to write().flexibleSearchQueries().easily() in compile-time safe manner without a need to remember the syntax.
Stars: ✭ 15 (-61.54%)
Mutual labels:  builder-pattern
SwiftBuilder
SwiftBuilder is a fast way to assign new value to the property of the object.
Stars: ✭ 26 (-33.33%)
Mutual labels:  builder-pattern

java-builder-pattern-tricks

The humble java builder pattern has been described frequently but is nearly always described in a very basic form without revealing its true potential! If you push the pattern a bit harder then you get can less verbosity in your API and more compile-time safety.

So what are these extra tricks?

The open-source library rxjava2-jdbc uses all these tricks to make the API easier to use.

java-builder2 is a library to generate builders.

What's the basic builder pattern for?

  • constructor field assignments can be mixed up
  • constructor calls are not very readable because Java doesn't support named parameters

The basic builder pattern

Let's start with a basic builder pattern and then we'll improve it. We'll consider how to build a Book object.

Our Book object has:

  • a mandatory author field
  • a mandatory title field
  • an optional category field
public final class Book {
    // Make fields final so we always know we've missed assigning one in the constructor!
    private final String author;
    private final String title;
    private final Optional<String> category;
    
    //should not be public
    private Book(Builder builder) {
        //Be a bit defensive
        Preconditions.checkNotNull(builder.author);
        Preconditions.checkArgument(builder.author.trim().length() > 0);
        Preconditions.checkNotNull(builder.title);
        Preconditions.checkArgument(builder.title.trim().length() > 0);
        Preconditions.checkNotNull(category);
        this.author = builder.author;
        this.title = builder.title;
        this.category = builder.category;
    }
    
    public String author() {
        return author;
    }
    
    public String title() {
        return title;
    }
    
    public Optional<Category> category() {
       return category;
    }
    
    public static Builder builder() {
        return new Builder();
    }
    
    public static final class Builder {
        String author;
        String title;
        Optional<String> category = Optional.empty();
        
        // should not be public to force use of the static builder() method
        private Builder() {
        }
        
        public Builder author(String author) {
            this.author = author;
            return this;
        }
        
        public Builder title(String title) {
            this.title = title;
            return this;
        }
        
        public Builder category(String category) {
            this.category = Optional.of(category);
            return this;
        }
        
        public Book build() {
            return new Book(this);
        }   
    }
}

To use this basic builder:

Book book = Book.builder().author("Charles Dickens").title("Great Expectations")
    .category("Novel").build();

Note that I haven't prefixed the methods with set or with. Seems like pointless noise to me but go with whatever style you like.

So that example looks ok but we can do better.

Making the basic builder better

Trick 1: Formatting

The first thing I'll note is that when you're chaining methods you end up with something much more readable when you put one method per line. You can force an IDE to honour this when it formats code by adding an empty comment at the end of each line:

Book book = Book //
  .builder() //
  .author("Charles Dickens") //
  .title("Great Expectations") //
  .category("Novel") //
  .build();

Trick 2: Shortcut the builder() method

The builder() method call is unnecessary if you have a mandatory field. We can replace that call with a call to one or many of the mandatory fields like author:

So instead of the declaration:

public Builder builder() {
    return new Builder();
}

we put:

public Builder author(String author) {
     return new Builder().author(author);
}

and we reduce the visibility of the Builder.author(String) method (because it has already been set).

Now we can call:

Book book = Book //
  .author("Charles Dickens") //
  .title("Great Expectations") //
  .category("Novel") //
  .build();

Saved one line, nice!

By incorporating author as a final field in the builder and passing it in the constructor we can clean up the internals a bit more:

public final class Book {
    // Make fields final so we always know we've missed assigning one in the constructor!
    private final String author;
    private final String title;
    private final Optional<String> category;
    
    private Book(Builder builder) {
        //Be a bit defensive
        Preconditions.checkNotNull(builder.author);
        Preconditions.checkArgument(builder.author.trim().length() > 0);
        Preconditions.checkNotNull(builder.title);
        Preconditions.checkArgument(builder.title.trim().length() > 0);
        Preconditions.checkNotNull(category);
        this.author = builder.author;
        this.title = builder.title;
        this.category = builder.category;
    }
    
    public String author() {
        return author;
    }
    
    public String title() {
        return title();
    }
    
    public Optional<Category> category() {
       return category;
    }
    
    public static Builder author(String author) {
        return new Builder(author);
    }
    
    public static final class Builder {
        final String author;
        String title;
        Optional<String> category = Optional.empty();
        
        private Builder(String author) {
            this.author = author;
        }
       
        public Builder title(String title) {
            this.title = title;
            return this;
        }
        
        public Builder category(String category) {
            this.category = Optional.of(category);
            return this;
        }
        
        public Book build() {
            return new Book(this);
        }     
    }
}

Note that you may prefer to continue using the builder() method particularly if the class already has a number of public static methods and distinguishing your builder entry method is difficult.

Trick 3: Enforce mandatory fields at compile time with builder chaining

Now let's improve the builder some more. We have to consider the Book object itself. It has two mandatory fields author and title and one optional field category.

As the builder stands so far we have a runtime check on the mandatory fields:

// will throw NullPointerException because title missing!
Book book = Book
  .author("Charles Dickens")
  .build();

It would be even better to get compile-time indication and this is something we can achieve with the builder chaining trick:

public final class Book {
    // Make fields final!
    private final String author;
    private final String title;
    private final Optional<String> category;
    
    private Book(Builder builder) {
        //Be a bit defensive
        Preconditions.checkNotNull(builder.author);
        Preconditions.checkArgument(builder.author.trim().length() > 0);
        Preconditions.checkNotNull(builder.title);
        Preconditions.checkArgument(builder.title.trim().length() > 0);
        Preconditions.checkNotNull(category);
        this.author = builder.author;
        this.title = title;
        this.category = category;
    }
    
    public String author() {
        return author;
    }
    
    public String title() {
        return title();
    }
    
    public Optional<Category> category() {
       return category;
    }
    
    public static Builder author(String author) {
        return new Builder(author);
    }
    
    public static final class Builder {
        final String author;
        String title;
        Optional<String> category = Optional.empty();
        
        private Builder(String author) {
            this.author = author;
        }
        
        Builder2 title(String title) {
            this.title = title;
            return new Builder2(this);
        }
    }
    
    public static final class Builder2 {
        final Builder b;
        
        Builder2(Builder b) {
            this.b = b;
        }
        
        Builder2 category(String category) {
            b.category = Optional.of(category);
            return this;
        }
                
        public Book build() {
            return new Book(b);
        }     
    }
}

Now if we try the previous example it won't compile. That's really good because finding bugs at compile time is MUCH better than finding out at runtime!

// will not compile!
Book book = Book
  .author("Charles Dickens")
  .build();

In fact, the new builder forces us to follow an exact method order in that you can't swap the order of author,title and category methods.

Book book = Book
  .author("Charles Dickens")
  .title("Great Expectations")
  .category("Novel")
  .build();

Hey category is an optional parameter isn't it? Let's test that:

Book book = Book
  .author("Charles Dickens")
  .title("Great Expectations")
  .build();

Yep that compiles, no problem.

Trick 4: Remove final build() call when all fields mandatory

See that final .build() method? Sometimes we can get rid of that too. If every field was mandatory and they were all builder chained then you could do this:

Book book = Book
  .author("Charles Dickens")
  .title("Great Expectations")
  .category("Novel");

That's one less line of code again which is nice but can only be achieved when all fields are mandatory.

To achieve this just replace this code:

        Builder2 category(String category) {
            b.category = Optional.of(category);
            return this;
        }
                
        public Book build() {
            return new Book(b);
        }   

with

        Book category(String category) {
            b.category = Optional.of(category);
            return new Book(b);
        }

Trick 5: Build generic signatures

To demonstrate the building of generic signatures, we'll show a contrived example with Tuples.

I want a class now that builds typed Tuples but I want a standard build method that honours generic types:

Type2<Integer, String> t2 = Tuples
  .value(12)
  .value("thing")
  .build();
Type3<Integer, String, Date> t3 = Tuples
  .value(12)
  .value("thing")
  .value(new Date())
  .build(); 

This api is achieved using builder chaining where each chained builder adds another generic signature:

public final class Tuple2<A,B> {
    public final A value1;
    public final B value2;
    public Tuple2(A value1, B value2) {
      this.value1 = value1;
      this.value2 = value2;
    }
}

public final class Tuple3<A,B,C> {
    public final A value1;
    public final B value2;
    public final C value3;
    public Tuple2(A value1, B value2, C value3) {
      this.value1 = value1;
      this.value2 = value2;
      this.value3 = value3;
    }
}

public final class Tuples {

    public static <T> Builder1<T> value(T t){
        return new Builder1<T>(t);
    }
    
    public static final class Builder1<A> {
        private final A value1;
        private Builder1(A value1) {
            this.value1 = value1;
        }
        public <B> value(A value2) {
            return new Builder2<A, B>(value1, value2);
        }
    }
    
    public static final class Builder2<A, B> {
        private final A value1;
        private final B value2;
        private Builder2(A value1, B value2) {
            this.value1 = value1;
            this.value2 = value2;
        }
        public <C> Builder3<A, B, C> value(C value3) {
            return new Builder3<A, B, C>(value1, value2);
        }
        
        public Tuple2<A,B> build() {
            return new Tuple2<A,B>(value1, value2);
        }
    }
    
    public static final class Builder3<A, B, C> {
        private final A value1;
        private final B value2;
        private final C value3;
        private Builder3(A value1, B value2, C value3) {
            this.value1 = value1;
            this.value2 = value2;
            this.value3 = value3;
        }
        
        //could add a Builder4 to keep chaining!
        
        public Tuple3<A, B, C> build() {
            return new Tuple3<A, B, C>(value1, value2, value3);
        }
    }
}

Trick 6: Improve discoverability

If your builder is to create a Thing then ideally they should be able to start the builder via a static method on Thing:

Thing thing = Thing.name("FRED").sizeMetres(1.5).create();

If you can't add a method to Thing (you might not own the api) then favour use of Things:

Thing thing = Things.name("FRED").sizeMetres(1.5).create();

For clarity reasons (for example if the class already has a lot of public static methods) if you want to use a builder method then go for it:

Thing thing = Thing.builder().name("FRED").sizeMetres(1.5).create();

Trick 7: Build lists succinctly

Using a special chaining trick we can support the building of lists like this:

Group
  .name("friends")
  .firstName("John")
  .lastName("Smith")
  .yearOfBirth(1965)
  .firstName("Anne")
  .lastName("Jones")
  .build();

In the example above firstName and lastName is mandatory and yearOfBirth is optional.

You see that we didn't need to return back to the top level builder to add the next person. This is achieved by adding methods pointing back to the top level builder from the person builder both for creating the next person but also for building the group if the list has finished. Here's all the code:

public final class Group {

    private final String name;
    private final List<Person> persons;

    public Group(String name, List<Person> persons) {
        this.name = name;
        this.persons = persons;
    }

    public String name() {
        return name;
    }

    public List<Person> persons() {
        return persons;
    }

    public static Builder name(String name) {
        Preconditions.checkNotNull(name);
        return new Builder(name);
    }

    public static final class Builder {
        final String name;
        List<Person> persons = new ArrayList<>();

        Builder(String name) {
            this.name = name;
        }

        public Builder2 firstName(String firstName) {
            Preconditions.checkNotNull(firstName);
            return new Builder2(this, firstName);
        }

        public Group build() {
            return new Group(name, persons);
        }
    }

    public static final class Builder2 {

        private final Builder b;
        private final String firstName;
        private String lastName;
        private Optional<Integer> yearOfBirth = Optional.empty();

        Builder2(Builder b, String firstName) {
            this.b = b;
            this.firstName = firstName;trick-7-build-lists-succintly
        }

        public Builder3 lastName(String lastName) {
            Preconditions.checkNotNull(firstName);
            this.lastName = lastName;
            return new Builder3(this);
        }
    }

    public static final class Builder3 {

        private final Builder2 person;

        Builder3(Builder2 person) {
            this.person = person;
        }

        public Builder3 yearOfBirth(int yearOfBirth) {
            person.yearOfBirth = Optional.of(yearOfBirth);
            return this;
        }

        public Builder2 firstName(String firstName) {
            Person p = new Person(person.firstName, person.lastName, person.yearOfBirth);
            person.b.persons.add(p);
            return person.b.firstName(firstName);
        }

        public Group build() {
            return person.b.build();
        }
    }

    public static final class Person {
        private final String firstName;
        private final String lastName;

        private final Optional<Integer> yearOfBirth;

        public Person(String firstName, String lastName, Optional<Integer> yearOfBirth) {
            this.firstName = firstName;
            this.lastName = lastName;
            this.yearOfBirth = yearOfBirth;
        }

        public String firstName() {
            return firstName;
        }

        public String lastName() {
            return lastName;
        }

        public Optional<Integer> yearOfBirth() {
            return yearOfBirth;
        }
    }
}

Conclusion

With luck you've seen that there is more power to the builder pattern than you realized and users of your APIs will benefit!

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].