Building objects with the Builder Pattern

Another pattern used to create objects is the builder pattern.

Intent: If you needed to create an object that requires many values to be set at the time the object is instantiated.

Motivation: With the data objects in an application growing, the constructors also grow to contain many attributes. For example, in the Book class example in the immutable pattern, we had three input parameters: genre, totalPages and authors. If the list of inputs grows with five new attributes added to the object, we’d need to add five new values to the constructor. Users who reference our object would be also required to update their constructor calls each time our object was modified, resulting in a class that is hard to manage.
The problem of the constructor growing too large has a name, referred to as telescoping constructor anti-pattern.

Solution: The builder pattern is a creational pattern in which parameters are passed to a builder object, often through a method chaining, and an object is generated with a final build call. It is often used with immutable objects, since immutable objects do not have setters and must be created with all their parameters set, although it can be used with mutable objects.
The following example is a BookBuilder class that uses the Book class:


public final class Book {
    private final String title;
    private final String genre;
    private final int totalPages;
    private final List<String> authors;
 
    public Book(String title, String genre, 
                int totalPages, List<String> authors ){
        this.title = title;
        this.genre = genre;
        this.totalPages = totalPages;
        if(authors == null){
            throw new RuntimeException(
                      "The author is required");
        }
        this.authors = new ArrayList<>(authors);
    }

    public String getTitle() {
        return title;
    }
 
    public String getGenre() {
        return genre;
    }

    public int getTotalPages() {
        return totalPages;
    }

    public String getAuthor(int index) {
        return authors.get(index);
    }
}

And the BookBuilder class would look like:


public class BookBuilder {
    private String title;
    private String genre;
    private int totalPages;
    private List<String> authors;
 
    public BookBuilder setTitle(String title) {
        this.title = title;
        return this;
    }

    public BookBuilder setGenre(String genre) {
        this.genre = genre;
        return this;
    }

    public BookBuilder setTotalPages(int totalPages) {
        this.totalPages = totalPages;
        return this;
    }

    public BookBuilder setAuthors(List<String> authors) {
        this.authors = authors;
        return this;
    }
 
    public Book build(){
        return new Book(title,genre,totalPages,authors);
    }
}

 

Things to note:

  • The BookBuilder class is mutable whereas Book is immutable.
  • All the setter methods return an instance of the builder object this. Builder methods are usually chained together, often callable in any order.
  • When creating the builder object, we might not set some properties that are not required. To make some fields required, we could write our build() method to throw an exception if certain required fields are missing.
  • Oftentimes, the builder objects are used once and then discarded, the examples below are uses of the builder. The second one does not save an instance to our builder object:

 BookBuilder bookBuilder = new BookBuilder();
 bookBuilder
     .setGenre("Fiction")
     .setTotalPages(416)
     .setAuthors(Arrays.asList("Stephen King"));
 Book bookToFind = bookBuilder.build();
 
 Book anotherBook = new BookBuilder()
     .setGenre("Technical")
     .setTotalPages(500)
     .setAuthors(Arrays.asList("Eric Gamma"))
     .build();

 

Leave Comment

Your email address will not be published. Required fields are marked *