Architecture & DesignWorking With Design Patterns: Builder

Working With Design Patterns: Builder

In a library, a catalog is a collection of various materials–books and movies (see Listing 1). Libraries print various reports on a frequent basis, including an inventory report that lists all materials in the library, sorted first by material type. (It’s a rather large report, I’ll bet!)

Listing 1: Basic materials.

// Material.java:
public class Material {
   private String classification;
   private String title;
   private String director;
   private String year;

   public Material(String classification, String title,
                   String director, String year) {
      this.classification = classification;
      this.title = title;
      this.director = director;
      this.year = year;
   }

   public String getAuthor() {
      return director;
   }

   public String getClassification() {
      return classification;
   }

   public String getTitle() {
      return title;
   }

   public String getYear() {
      return year;
   }
}

// Book.java:
public class Book extends Material {
   public Book(String classification, String title,
               String author, String year) {
      super(classification, title, author, year);
   }
   // ...
}

// Movie.java:
public class Movie extends Material {
   public Movie(String classification, String title,
                String director, String year) {
      super(classification, title, director, year);
   }

   public String getDirector() {
      return getAuthor();
   }
   // ...
}

The Catalog class is a simple container of materials. See Listing 2.

Listing 2: Catalog.

package builder;

import java.util.*;

public class Catalog implements Iterable<Material> {
   private List<Material> materials = new ArrayList<Material>();

   public void add(Material material) {
      materials.add(material);
   }

   public List<Material> materials() {
      return materials;
   }

   @Override
   public Iterator<Material> iterator() {
      return materials.iterator();
   }
}

Reports are produced using a simple construction process. First, sort the list of materials, grouping them by type (for example, books first, and then movies). Print an appropriate header for the report. Then, iterate through the sorted list of materials, and output each row. Finally, print any appropriate additional information at the end of the report.

The details behind each of these construction steps vary. For example, a web report requires appropriate HTML tags. Or perhaps a constrained-width printout (perhaps a summary receipt) requires truncating some fields and reformatting the output.

The builder pattern provides an ideal way to organize the design of such a report generator. Using the builder pattern, a client drives the construction of complex objects by specifying their type and content. The client, referred to as the “director” in the builder pattern, doesn’t specify any of the implementation details—that job is left to a builder hierarchy.

Listing 3 shows the director class. The construction algorithm is expressed as one line in the generate method: The Catalog object is first sorted using the sort function, and passed to the createReport method, along with the appropriate builder object. Code in createReport makes a few calls back to the builder object: First to tell the builder to generate a header, then to tell it to generate detail output for each material object, and finally to tell it to generate a footer. This createReport method represents the “shell” of the construction algorithm.

Ultimately, the builder coughs up the report (from getReport). The director returns this report to the calling client.

Listing 3: The director.

import java.util.*;

public class CatalogReportDirector {
   private final Catalog catalog;

   public CatalogReportDirector(Catalog catalog) {
      this.catalog = catalog;
   }

   public String generate(CatalogReportBuilder builder) {
      return createReport(builder, sort(catalog));
   }

   private List<Material> sort(Catalog catalog) {
      List<Material> catalogCopy =
         new ArrayList<Material>(catalog.materials());
      Collections.sort(catalogCopy, new Comparator<Material>() {
         @Override
         public int compare(Material material1,
                            Material material2) {
            if (material1.getClass() == material2.getClass()) {
               if (material1.getTitle().
                  equals(material2.getTitle()))
                  return material1.getAuthor().
                     compareTo(material2.getAuthor());
               return material1.getTitle().
                  compareTo(material2.getTitle());
            }
            return material1.getClass().getName().compareTo(
                  material2.getClass().getName());
         }
      });
      return catalogCopy;
   }

   private String createReport(CatalogReportBuilder builder,
         List<Material> sortedCatalog) {
      builder.generateHeader();
      for (Material material: sortedCatalog)
         builder.generateDetail(material);
      builder.generateSummary();
      return builder.getReport();
   }
}

The fact that the client is responsible for creating and passing a builder object to the director is another nuance of the builder pattern.

So, what do builders look like? Listing 4 shows the structure of a builder, represented as an abstract class. Listing 5 shows the implementation for an HTML builder.

Listing 4: The builder.

public abstract class CatalogReportBuilder {
   static final String EOL = System.getProperty("line.separator");
   private StringBuilder report = new StringBuilder();

   public String getReport() {
      return report.toString();
   }

   abstract public void generateHeader();
   abstract public void generateDetail(Material material);
   abstract public void generateSummary();

   protected void appendLine(String text) {
      report.append(text);
      report.append(EOL);
   }
}

The builder class holds on to a report reference. As portions of the construction algorithm execute, the report fills in. Listing 6 shows what an alternate builder might look like.

Listing 5: An HTML builder.

public class HtmlCatalogBuilder extends CatalogReportBuilder {
   @Override
   public void generateDetail(Material material) {
      appendLine(String.format("<tr>%s%s%s%s</tr>",
            td(material.getTitle()),
            td(material.getAuthor()),
            td(material.getClassification()),
            td(material.getYear())));
   }

   @Override
   public void generateHeader() {
      appendLine("<body>");
      appendLine("<table>");
      appendLine(String.format("<tr>%s%s%s%s</tr>",
            th("Title"),
            th("Auth/Dir"),
            th("Classification"),
            th("Year")));
   }

   @Override
   public void generateSummary() {
      appendLine("</table>");
      appendLine("</body>");
   }

   private String td(String element) {
      return tag(element, "td");
   }

   private String th(String element) {
      return tag(element, "th");
   }

   private String tag(String element, String tag) {
      return String.format("<%s>%s</%s>", tag, element, tag);
   }
}

Listing 6: An alternate builder.

public class PrintCatalogBuilder extends CatalogReportBuilder {
   private static final String ROW_LAYOUT = "%s %s %s %s";
   private static final int YEAR_WIDTH = 4;
   private static final int CLASSIFICATION_WIDTH = 14;
   private static final int AUTHOR_WIDTH = 20;
   private static final int TITLE_WIDTH = 24;

   @Override
   public void generateDetail(Material material) {
      appendLine(format(
            material.getTitle(),
            material.getAuthor(),
            material.getClassification(),
            material.getYear()));
   }

   @Override
   public void generateHeader() {
      String header = format("Title", "Auth/Dir",
                             "Classification", "Year");
      appendLine(header);
      appendLine(replaceWithDashes(header));
   }

   private String replaceWithDashes(String header) {
      return header.replaceAll("S", "-");
   }

   @Override
   public void generateSummary() {
      appendLine("");
   }

   private String format(
         String title, String author, String classification,
         String year) {
      return String.format(ROW_LAYOUT,
            pad(title, TITLE_WIDTH),
            pad(author, AUTHOR_WIDTH),
            pad(classification, CLASSIFICATION_WIDTH),
            pad(year, YEAR_WIDTH));
   }

   private String pad(String text, int width) {
      if (text.length() >= width)
         return text.substring(0, width);

      StringBuilder spaces = new StringBuilder(text);
      for (int i = 0; i < width - text.length(); i++)
         spaces.append(' ');
      return spaces.toString();
   }
}

The builder pattern is reminiscent of the Template Method pattern. When using Template Method, a base class defines an algorithm structure as well as the common details required to implement that algorithm. Derived classes complete the algorithm by “filling in the holes.” The builder pattern primarily differs in that its job is to construct objects, not drive an algorithm. It also represents a “separation” of the template method—the algorithm is defined in one class (the director) but implemented in a separate builder hierarchy.

The builder pattern is a prime example of clean separation of responsibilities, as well as separation of interface and implementation. Use of the pattern also prevents what otherwise might be considerable duplication.

Figure 1: Builder.

About the Author

Jeff Langr is a veteran software developer with over a quarter century of professional software development experience. He’s written two books, including Agile Java: Crafting Code With Test-Driven Development (Prentice Hall) in 2005. Jeff has contributed a couple chapters to Uncle Bob Martin’s upcoming book, Clean Code. Jeff has written over 75 articles on software development, with over thirty appearing at Developer.com. You can find out more about Jeff at his site, http://langrsoft.com, or you can contact him via email at jeff at langrsoft dot com.

Get the Free Newsletter!
Subscribe to Developer Insider for top news, trends & analysis
This email address is invalid.
Get the Free Newsletter!
Subscribe to Developer Insider for top news, trends & analysis
This email address is invalid.

Latest Posts

Related Stories