Design patterns - Builder

From Juneday education
Jump to: navigation, search

Description

In this chapter, we'll discuss a creational design pattern called Builder. We'll settle with two versions of this pattern (there exists more), namely a builder as a nested class to solve the problem of too many parameters to the constructor, and a bi-directional builder (based on an idea from Allen Holub) which uses Builders in an interesting way to decouple the interface to fields of an object from the object itself (getting rid of getters and setters altogether).

Classic Builder

The classic builder pattern is about creating complex aggregates (with many parts) from some source. The idea is to separate the building of a Java object representation from the parsing (interpreting) of the source. Having separated the parsing from the creation, you get the flexibility to use the same parsing to get different types of object representations from the builder.

It is often described as a Director following a procedure to create a Product. The director uses a Builder to assemble the Product and the steps are the same regardless of what specific type of product it is.

Examples from the real world could be a Director wanting to build a house (the Product). Regardless of type of house, the procedure is the same, makeFloor(), makeWalls(), makeRoof(). Using a Builder, we can abstract this process and have many concrete house builders, all offering the same assembly steps.

Another common example is the ordering of a burger meal. The Director works at the Counter and uses the staff as builders of a meal. The Director aske the builder to addMainCourse() (e.g. a burger), addSideOrder() (e.g. fries) and addDrink() (e.g. a milkshake). Regardless of the customer's choice, the process for making a meal is the same.

In computing, the Director can read a file from disk and create a representation which can be converted to many formats. The Director might parse a file and ask the builder to assemble a representation which later can be exported to some other format (PDF, XML, HTML etc). The Director would perhaps parse the file and find all headings, paragraphs, lists, tables etc, everytime calling the builder's matching method: makeHeading(), makeParagraph(), makeList(), makeListItem() etc, etc. When the Director is done parsing the file, it calls build() (or perhaps in this case getDocument()) and the result is an object of some type (perhaps XML or PDF).

Making the Builder an abstract class or an interface allows for many concrete builders which all works the same but create different type of target products (different Documents like HTML, XML, PDF etc).

An example from the Java API is the javax.xml.parsers package:

    DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
    DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
    Document doc = docBuilder.newDocument();
    Element rootElement = doc.createElement("COURSES");
    rootElement.setAttribute("studentName", "Beata Bengtsson");
    rootElement.setAttribute("studentID", ""+id);
    doc.appendChild(rootElement);

    Element course        = doc.createElement("COURSE");
    course.appendChild(doc.createTextNode("TIG058"));
    rootElement.appendChild(course);
    course                = doc.createElement("COURSE");
    course.appendChild(doc.createTextNode("TIG059"));
    rootElement.appendChild(course);

The abstract Document created above can later be transformed into for instance XML using a Transformer (from a TransformerFactory - we'll talk about factories in a separate chapter!):

    TransformerFactory transformerFactory = TransformerFactory.newInstance();
    Transformer transformer = transformerFactory.newTransformer();
    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
    DOMSource source = new DOMSource(doc);
    StreamResult result = new StreamResult(new File("Student.xml"));
    transformer.transform(source, result);

Other examples of various kinds of Builders of the Java API include java.text.CalendarBuilder, java.lang.StringBuilder, java.lang.ProcessBuilder and com.sun.org.apache.xml.internal.utils.DOMBuilder. Feel free to explore them and/or find usage examples and try to understand how they work.

For a discussion and examples of classic Builders, please read about it here (Sourcemaking on the Builder pattern).

Making a class have its own builder

In this chapter, however, we'll show you two interesting uses of the Builder pattern. The first one is to replace the need for constructor in a class where the constructor otherwise would need to offer a long list of parameters. The objective here is to eliminate the use of extremely complicated constructor calls where the client of the class would need to be very careful about what argument matches what parameter in the constructor.

As a general rule (or design goal), we don't want objects to be created with an illegal state. What this means, is that we want mechanisms for creating objects which guarantees that all mandatory fields will be initialized. At the same time, we want to provide flexibility about fields which are optional.

In the video lecture, we take a complex class representing a reservation on the Moon space shuttle as an example. The class has a lot of fields:

  // Mandatory fields
  private int seatClass;
  private int numSeats;
  private Date day;
  // Optional fields
  private int numMeals;
  private boolean isWindowSeat;
  private boolean isNonSmoking;
  private boolean hasTable;

Now, imagine if we'd want to provide constructors to cover all possible combinations of parameters to initialize all those fields. We'd start with a constructor which takes the three mandatory fields as parameters. But then what? A particular problem is that there are three optional booleans. Parameters of the same type is particularly problematic because we must remember the correct order of them when we send the arguments via the new operator. Imagine a call like this:

new Reservation(seatClass, numSeats, date, numMeals, isWindow, isNonSmoking, hasTable);

Seven arguments, yikes! To make it obvious that this is hard, this is what it could look like if we didn't use variables in the call:

Date date =  = new SimpleDateFormat("yyyy-MM-dd").parse("2020-02-28");
new Reservation(1,3,date,3,false,true,false);

It should be obvious that it's easy to mix up the order of all those parameters and yet the code would compile (if we manage to get the types right but the values in the wrong order).

A solution proposed by Joshua Bloch (and others) is to re-write the Reservation class so that it contains a Builder (as a nested class - a nested class is a static inner class), and make the constructor private.

Now, in order to make a reservation, we'd need to use the Reservation class' builder. We can create the builder so that it has only one constructor, which takes the mandatory fields as parameters. Then the builder can provide methods for adding the optional fields (in any order we'd like, because now the methods have descriptive names). To get the reference to the finished product (the Reservation), we'd call build() on the builder:

    Reservation res = new Reservation.Builder
      (day, Reservation.FIRST_CLASS, 2)
      .numMeals(2)
      .isNonSmoking(true)
      .isWindowSeat(true)
      .build();

As you see, we don't have to provide all optional fields (above, the hasTable is not set and defaults to false). Another feature is, like we said above, that the order in which we call the methods on the builder is unimportant. So we could create another Reservation like this:

    Reservation other = 
      new Reservation.Builder(day, Reservation.ECONOMY_CLASS, 1)
      .isWindowSeat(true)
      .numMeals(4)
      .isNonSmoking(true)
      .hasTable(true)
      .build();

The Java API has similar constructs for some of its classes. StringBuilder is a builder (doesn't use a nested class) and provides the append(String) method, and can be used as a mutable String. This has the advantage (over String) that we'd only need one object to build up a String representation in steps (whereas String and the plus operator would create a lot of temporary objects).

An example is shown below:

  public String toString(){
    return new StringBuilder(name)
      .append(",")
      .append(email)
      .append(",")
      .append(phone)
      .append("\n")
      .toString();
  }

You can find the complete source code from the video lecture with the Reservation example here (github).

A bi-directional builder pattern

This is an interesting adaptation of the builder pattern, which was introduced by Allen Holub in an article at JavaWorld. The article was actually a follow-up to a previous article by the same author, arguing that getters and setters are evil. In the article, the author shows that it is possible to create a user interface from an object which has no getters or setters.

The basic idea is that, using builders, we can decouple accessing fields from the class as well as the initialization and creation of the class. In the video lecture in this chapter, about bi-directional builders (this is not a standard term, simply a description we came up with), we show how this can be achieved using a simple class for Contact objects (like the ones in a contact book).

There are two major features in this version of the builder pattern. First of all, the Contact class defines two inner interfaces (these automatically becomes static members of the class), Importer and Exporter:

public class Contact{
  private Email email;
  private Name name;
  private Phone phone;

  public interface Exporter{
    void addName(String name);
    void addEmail(String email);
    void addPhone(String phone);
  }

  public interface Importer{
    String provideName();
    String provideEmail();
    String providePhone();
    void   open();
    void   close();
  }

  /* constructor and the rest */
}

The Contact.Importer interface defines an API for classes which can provide the data for the construction and initialization of Contact. So the constructor of Contact now becomes:

  // Constructs a Contact from the builder provided
  public Contact(Importer builder){
    builder.open();
    this.name  = new Name(builder.provideName());
    this.email = new Email(builder.provideEmail());
    this.phone = new Phone(builder.providePhone());
    builder.close();
  }

The open() and close() methods allow for implementing classes to manage resources (like opening files, connecting to a database etc). So, with this information, we see that the way to construct a Contact is to create an instance of a class implementing the Contact.Importer interface, and send a reference to it to the constructor of Contact. Here's a code example of client code creating a Contact:

Contact c = new Contact(new ContactTextImporter());

The ContactTextImporter could for instance use a text-base interaction with the user, or read a contact from a text file. The constructor uses the importer builder and kindly asks it for the information it needs to initialize the instance variables name, email and phone. An important aspect to notice here, is that the importer builder doesn't need to know the types of these variables, it will have to follow the interface specification and use String for all of these. The constructor could very well convert these Strings to some other types if necessary. In this simple example, Contact too is using String for all three variables, however.

This new constructor together with the Contact.Importer interface decouples the creation of a Contact from the class itself. The Contact class remains unaware of where these values come from (how they are gathered). This is of course similar to a usual constructor, but we've seen examples where the constructor opens files etc to accomplish its intialization, which is something we strongly advise against. What we mean here with decoupling, is the fact that the constructor in Contact knows nothing of the class of the importer, just its super type Contact.Importer (which is an interface). This provides the flexibility for client code to use any number of implementations for Contact.Importer and they will all work equally well with the constructor.

The Contact.Exporter interface is used to export the state of a contact (the values of its fields, in this implementation as Strings) without separate getters for each field. Instead the Contact class provides an export() method which takes a reference to a Contact.Exporter object and provides it with the values:

  // Exports this contact to the builder provided as argument
  public void export(Exporter builder){
    builder.addName(name.toString());
    builder.addEmail(email.toString());
    builder.addPhone(phone.toString());
  }

The motivation for this method is to decouple how values are exported (and used) to an exporter builder. This way, client code can create any number of implementations of Contact.Exporter to use as argument to the export() method. For instance, a Contact could export its state to an exporter builder which could create a graphical user interface widget presenting the contact. The thing is, the Contact class, being decoupled and all, doesn't know and doesn't need to know what the exporter builder is doing. From the Contact objects point of view, it is happy to provide its state to an exporter builder when asked via the export() method.

This is what it could look like, when a Contact exports itself to a GUI builder, which is later used in a GUI window:

    GUIExporter gui = new GUIExporter();
    c.export(gui);
    new SwingDisplay(gui.getJPanel());

First, the client code creates a GUIExporter object (whose class implements Contact.Exporter). Next, the client code asks a Contact to export its stuff to this object. Finally, a GUI window is created and a JPanel is given to the constructor of this window. The JPanel comes from the GUIExporter object. The window can now display information about this Contact, without calling any getters. Actually, the window component is totally decoupled from the Contact class and knows nothing about Contacts.

Another benefit, apart from being able to export the state to for instance a GUI builder, is that the same method could be used to export the state to a builder which can store the state in a database, a file or even over the network. It is possible to write an exporter builder which can convert the Contact to XML, HTML, CSV, JSON or some other format:

    ContactHTMLExporter html = new ContactHTMLExporter();
    c.export(html);
    System.out.println("HTML:\n" + html);
    ContactCSVExporter csv = new ContactCSVExporter();
    c.export(csv);
    System.out.println("CSV:\n"+csv);

As you see, the client code can use the same Contact to export itself to various formats or other destinations (as a GUI component for instance, or as an HTTP POST request to a server etc).

What have we gained from this adaptation of the builder pattern? Well, what we hope to have shown is a way to make the Contact class completely unaware of how to save itself to a file or present itself in a GUI. Instead it only knows on a very abstract level how to export its state to a component which can build or do whatever it wants with this information. On the other side, we hope to have got you thinking about the GUI window (above represented by the SwingDisplay class) which could be instantiated with a ready-made JPanel with contact information. So this SwindDisplay class can display a representation of a Contact without even knowing that there exists a Contact class (hence removing the need to send a Contact reference to the Window and, in particular, removing the need for getters in the Contact class).

The style promoted in this design, is sometimes called "tell-don't-ask". The idea is to remove the typical calls to the getters (which would be "asking the object about its state"). Instead, the "tell-don't-ask" school promotes that we should tell the object to perform some task for us, and provide the information needed for the object to complete its task (asking it to export itself to a builder is an example of that). When you think about it, the semantics of a getter like getName() is a little backwards. We ask the contact object to "get name" and provide no information about where to get it from or where to put it. From the perspective of the client code, getName() makes more sense - the client code wants to get the name from the contact object. But this is, according to some, a typical procedural way of thinking - "objects are simply vessels of data which we get this data from when we need it". The proponents of "tell-don't-ask" instead suggest that we shouldn't view objects like simple carriers of data. The objects have methods too, and for a reason. The objects should be considered first-class citizens, they suggest, and we should trust them to be capable of performing stuff for us.

In a more procedural design, perhaps we'd send a reference to a Contact to the window. Then the window would ask the object for its name, email and phone and use these values to build the gui representation of the Contact, typically using getName(), getEmail() and getPhone(). Or at least, the designer would send a Contact reference to a component capable of creating a JPanel (like in the example) and the component building the JPanel would go on with the getters.

Which design is the "right way" ™ we leave up to you to decide. The point of this example was to present an alternative to the typical way Java is being taught in books and courses, and if we can do that and talk about builders at the same time, why not!

Chapter videos

English videos

Exercises

Make accessors for the Reservation class

Download the source code for the Reservation class.

Identify the public interface of this class. Make a test program which creates a few Reservations. How can you access the information in a Reservation instance?

Make the class more useful in more contexts than simply printing it from a test program. In order to do this, create accessors for all the fields. You decide what type should be published by the accessors. We recommend that you use the following naming scheme:

  • Accessor for day - day()
  • Accessor for seatClass - seatClass()
  • etc

(You see? You don't have to name the getters getAttributeName() even if you decide to provide them!)

Make an Exporter interface and export() method for the Reservation class

As an exercises, make an Exporter interface as a member of the Reservation class. Also make an export() method which takes a Exporter as the only arguments and adds the current values to the methods of the interface.

Create an implementation in its own source code file and call it ReservationXMLExporter. The implementing class should have a toString() implementation which returns an XML fragment like the example below:

<reservation day="Fri Feb 28 00:00:00 CET 2020">
  <seats>2</seats>
  <meals>2</meals>
  <class>First Class</class>
  <placement table="true" nonsmoking="true" window="true" />
</reservation>

The ReservationXMLExporter should not use the accessors, instead it should copy the pattern given in the lecture.

Suggested solutions

Expand using link to the right to see the full content.

Proposed solution to Make accessors for the Reservation class

The public interface to Reservation is:

  public static final int FIRST_CLASS=0;
  public static final int SECOND_CLASS=1;
  public static final int ECONOMY_CLASS=2;
  public static class Builder
  public String toString()

Example code creating a few Reservations (and printing them to std out):

    Date day = new Date();
    try{
      day = new SimpleDateFormat("yyyy-MM-dd").parse("2020-02-28");
    }catch(Exception e){}
    Reservation res = new Reservation.Builder
      (day, Reservation.FIRST_CLASS, 2)
      .numMeals(2)
      .isNonSmoking(true)
      .isWindowSeat(true)
      .build();
    System.out.println(res);
    Reservation other = 
      new Reservation.Builder(day, Reservation.ECONOMY_CLASS, 1)
      .isWindowSeat(true)
      .numMeals(4)
      .isNonSmoking(true)
      .hasTable(true)
      .build();    
    System.out.println(other);

The only way to get information from a Reservation is through the toString() method. While this was enough for this simple example of a nested builder, it is not recommended as a general style. Only providing a toString() will very likely lead to client code trying to do smart things like writing parsers for the generated String to get individual values. The recommendation we give you (and we share this recommendations with others) is to provide accessors to every field included in the String representation returned from toString(). The reason is that as soon as the toString() method changes its format, all such parsers are bound to fail!

Here's the code for such accessors (note that we've factored out the string representation of the seat class - we shouldn't repeat ourselves, remember?).

  private String seatClassAsString() {
    String sClass="";
    switch(seatClass){
    case FIRST_CLASS:
      sClass="First class";
      break;
    case SECOND_CLASS:
      sClass="Second class";
      break;
    default:
      sClass="Economy class";
    }
    return sClass; 
  }
  public String seatClass() { return seatClassAsString(); }
  public int numSeats() { return numSeats; }
  public Date day() { return day; }
  public int numMeals() { return numMeals; }
  public boolean isWindowSeat() { return isWindowSeat; }
  public boolean isNonSmoking() { return isNonSmoking; }
  public boolean hasTable() { return hasTable; }
  
  @Override
  public String toString(){
    String sClass=seatClassAsString();
    return String
      .format("Trip on %s in %s for %d people %s %s %s %s",
              day.toString(),sClass,numSeats,
              (numMeals!=0?("with " + numMeals + " meals"):""),
              (isWindowSeat?"window seat":""),
              (isNonSmoking?"Non-smoking":"Smoking"),
              (hasTable?"with a table":"")).trim();
  }

We chose to make the accessor for seatClass of type String reference, but this was an arbitrary decision. On the one hand, it is convenient because clients don't have to use the constants in the class to interpret the int which seatClass holds. On the other hand, they have to use the constants when creating a Reservation anyway, so making the accessor of type int would be reasonable too. The best solution, perhaps, would have been to use an Enum instead of integer values as constants. Feel free to do that, if you feel it's a better solution.

Proposed solution to Make an Exporter interface for the Reservation class

The Exporter interface as an inner interface to Reservation:

  public interface Exporter{
    void addSeatClass(String seatClass);
    void addNumSeats(int numSeats);
    void addDay(Date day);
    void addNumMeals(int numMeals);
    void addIsWindowSeat(boolean isWindowSeat);
    void addIsNonSmoking(boolean isNonSmoking);
    void addHasTable(boolean hasTable);
  }

The export() method in Reservation:

  public void export(Exporter builder){
    builder.addSeatClass(seatClassAsString());
    builder.addNumSeats(numSeats);
    builder.addNumMeals(numMeals);
    builder.addDay(day);
    builder.addIsWindowSeat(isWindowSeat);
    builder.addIsNonSmoking(isNonSmoking);
    builder.addHasTable(hasTable);
  }

The ReservationXMLExporter class:

import java.util.Date;
public final class ReservationXMLExporter implements Reservation.Exporter{
  // Mandatory fields
  private String seatClass; // Using String!!!
  private int numSeats;
  private Date day;
  // Optional fields
  private int numMeals;
  private boolean isWindowSeat;
  private boolean isNonSmoking;
  private boolean hasTable;
  public void addSeatClass(String seatClass){ this.seatClass = seatClass; }
  public void addNumSeats(int numSeats){ this.numSeats = numSeats; }
  public void addDay(Date day){ this.day = day; }
  public void addNumMeals(int numMeals){ this.numMeals = numMeals; }
  public void addIsWindowSeat(boolean isWindowSeat){ this.isWindowSeat = isWindowSeat; }
  public void addIsNonSmoking(boolean isNonSmoking){ this.isNonSmoking = isNonSmoking; }
  public void addHasTable(boolean hasTable){ this.hasTable = hasTable; }

  @Override
  public String toString(){
    return new StringBuilder("<reservation day=\"")
      .append(day).append("\">\n")
      .append("  <seats>").append(numSeats).append("</seats>\n")
      .append("  <meals>").append(numMeals).append("</meals>\n")
      .append("  <class>").append(seatClass).append("</class>\n")
      .append("  <placement table=\"")
      .append(hasTable).append("\" nonsmoking=\"")
      .append(isNonSmoking).append("\" window=\"")
      .append(isWindowSeat).append("\" />\n")
      .append("</reservation>").toString();
  }
  public String toXML(){ return this.toString(); } 
}

Sample test code:

import java.util.Date;
import java.text.SimpleDateFormat;
public class Main{
  public static void main(String[] args){
    Date day = new Date();
    try{
      day = new SimpleDateFormat("yyyy-MM-dd").parse("2020-02-28");
    }catch(Exception e){}
    Reservation res = new Reservation.Builder
      (day, Reservation.FIRST_CLASS, 2)
      .numMeals(2)
      .isNonSmoking(true)
      .isWindowSeat(true)
      .build();
    System.out.println(res);
    Reservation other = 
      new Reservation.Builder(day, Reservation.ECONOMY_CLASS, 1)
      .isWindowSeat(true)
      .numMeals(4)
      .isNonSmoking(true)
      .hasTable(true)
      .build();    
    System.out.println(other);

    ReservationXMLExporter xml = new ReservationXMLExporter();
    res.export(xml);
    System.out.println(xml);
    other.export(xml);
    System.out.println(xml);
  }
}

Chapter links

Vimeo channel

  • All videos about builders Vimeo

Source code

  • Builder - Example with the Reservation class (github)
  • Bi-directional builder - Example with the Contact class (github)
  • Proposed solutions to all exercises (github)

Books this chapter is a part of

Further reading

Book TOC | previous chapter | next chapter