Michael Safyan

Builder

Builder Pattern

Overview

The "builder" pattern (or "options" pattern) is an object-oriented design pattern in which an intermediate, mutable object called a "builder"* or "options"* is used to select the various configuration options for constructing the desired object.

*Although it is customary to use "Builder" in the name (if it has a function that creates the final object) or "Options" in the name (if it is instead used as a constructor parameter or as a parameter to a factory function but does not, itself, have the creation function), any object that serves as a collection of (mostly) independent configuration options that are used to create some other object would be an example of this pattern, regardless of how the first object happens to be named.

When to Use

The purpose of a builder is to increase the simplicity and clarity of construction when there are many, largely independent options that each have reasonable defaults. Without a builder, the same result is achieved using a large number of constructors (for the cross-product of allowable combinations), each with different parameter lists, some with lots and lots of parameters). Aside from the fact that this produces a lot of constructors, many of which are repetitive and redundant, this also makes code that invokes those constructors confusing if the parameters are supplied positionally rather than by name (as is the case in C++ and Java); in languages that support named parameters that can be specified in any order and that can be assigned default values (as in Python), the need for such a pattern is less obvious.

In short, if you have a constructor that takes three or more parameters and some of the constructor parameters are optional, or if you have multiple constructors to handle various combinations of parameters, or if you have a constructor that takes simple scalar values (such as booleans) as parameters where it would add clarity to callers to specify the corresponding name rather than a simple positional literal value, a builder or options class might help.

When NOT to Use

You should not use a builder if the object you are constructing is mutable and provides a means of modifying the configuration values after the object has been constructed. You should also not use this pattern if there are very few constructors, the constructors are simple (e.g. only one or two parameters), or if there are no reasonable default values for any of the constructor parameters and all of them must be specified explicitly.

General Pattern

Java

       // This is the class that will be produced by the builder
       public class NameOfClassBeingCreated {
          // ...

          // This is the builder object
          public static class Builder {
            // ...

            // Each builder has at least one "setter" function for choosing the
            // various different configuration options. These setters are used
            // to choose each of the various pieces of configuration independently.
            // It is pretty typical for these setter functions to return the builder
            // object, itself, so that the invocations can be chained together as in:
            //
            //     return NameOfClassBeingCreated
            //         .newBuilder()
            //         .setOption1(option1)
            //         .setOption3(option3)
            //         .build();
            //
            // Note that any subset (or none) of these setters may actually be invoked
            // when code uses the builer to construct the object in question.
            public Builder setOption1(Option1Type option1) {
               // ...
               return this;
            }

            public Builder setOption2(Option2Type option2) {
               // ...
               return this;
            }

            // ...

            public Builder setOptionN(OptionNType optionN) {
               // ...
               return this;
            }

            // ...

            // Every builder must have a method that builds the object.
            public NameOfClassBeingCreated build() {
               // ...
            }

            // The Builder is typically not constructible directly
            // in order to force construction through "newBuilder".
            // See the documentation of "newBuilder" for an explanation.
            private Builder() {}
          }

          // Constructs an instance of the builder object. This could
          // take parameters if a subset of the parameters are required.
          // This method is used instead of using "new Builder()" to make
          // the interface for using this less awkward in the presence
          // of method chaining. E.g., doing "(new Foo.Builder()).build()"
          // is a little more awkward than "Foo.newBuilder().build()".
          public static Builder newBuilder() {
             return new Builder();
          }

          // ...

          // There is typically just one constructor for the class being
          // constructed that is private so that it may only be invoked
          // by the Builder's "build()" function. The use of the builder
          // allows for the class's actual constructor to be simplified.
          private NameOfClassBeingCreated(
              Option1Type option1,
              Option2Type option2,
              // ...
              OptionNType optionN) {
            // ...
          }
       }
    

C++

      // NOTE: Although the example for C++ could follow the pattern of Java
      // quite closely,the structure used in Java forces the object to be
      // constructed on the heap. As a result, it is common to see the
      // options pattern used in C++, instead, in cases where it is
      // desireable to allow the object to be allocated either on the
      // stack or the heap at the user's discretion (if it is desireable
      // to force construction on the heap, only, then more closely mimicking
      // the Java builder version may, in fact, be desireable).


      // It's good practice to put things in a namespace appropriate for your
      // project or subproject, hence we use this here in our example, but
      // this is not an important detail for the particular pattern.
      namespace somenamespace {

      // The options class could be declared inside the class that is
      // being created, but for simplicity of the definition (and to allow
      // the options class to be declared in a different header file from
      // the class that uses the options, itself), I find that
      // it is easier to define the class in a way that is not nested
      // but to then use a "typedef" to allow the nested name to be used.
      class NameOfClassBeingCreatedOptions {
       public:
         // There is typically just a default constructor that initializes
         // the options to the set of defaults.
         NameOfClassBeingCreatedOptions();

         // The options class has at least one setter function that returns a mutable
         // reference to the current object (via "return *this") so that multiple set
         // invocations can be chained together as in:
         //
         //      NameOfClassBeingCreated instance(
         //          NameOfClassBeingCreated::Options().set_foo(1).set_bar(2));
         //
         // In this example, I assume that the setter parameters are being passed by
         // const reference (which is most likely what you would want for user-defined
         // types), but these could also be passed by value (e.g. for builtins).
         NameOfClassBeingCreatedOptions& set_option_1(const Option1Type& option1);
         NameOfClassBeingCreatedOptions& set_option_2(const Option2Type& option2);
         // ...
         NameOfClassBeingCreatedOptions& set_option_N(const OptionNType& optionN);

       private:
         // ...
      };


      // The class that is constructed using the options
      class NameOfClassBeingCreated {
       public:
         // Allow the options to be referred to as NameOfClassBeingCreated::Options
         typedef NameOfClassBeingCreatedOptions Options;

         // Some constructor takes the options as a parameter (rather than having
         // multiple parameters that take various bits and pieces of the configuration
         // that the options class encapsulates). Note that if some parameters are
         // required, they are typically just listed as separate required parameters
         // before the options parameter rather than being listed in "Options" (or
         // instead of being made required constructor parameters of "Options").
         NameOfClassBeingCreated(const Options& options);

         // ...
       private:
         // ...
      };

      }  // namespace somenamespace
    
Good Examples

HTTP Fetch

This is a made up -- but extremely realistic -- example of a good use of a builder. The reason that this is a good example is that an HTTP request may contain many different, independent options, but not all of these options are needed (at the most basic, one would need just the URL and nothing else). Using a builder here allows callers to specify the subset of options that they care about and also makes the meaning of the parameters clearer. This is also a good example in that it demonstrates that the setter functions can, in some cases, take multiple parameters, and in some cases the builder may contain lists, sets, or maps that aren't simply overwritten with setters but support insertion operations.

      HttpRequest request =
          HttpRequest
              .newBuilder("http://some.domain.tld/some/path")
              .setTimeOut(1.0, TimeUnit.MILLISECONDS)
              .setFollowRedirects(true)
              .addHeader("Accept-Language", "en")
              .addHeader("Accept-Encoding", "gzip")
              .build();
       ListenableFuture<HttpResponse> eventualResult = requestQueue.submit(request);
       Futures.addCallback(eventualResult, responseReceivedHandler);
    

Chart

This is a made up but also reasonable example. The reason that this is a good example is that, once again, most of these options could reasonably be omitted and given default values, and different callers might wish to use a different subset of options. The one area where this example is somewhat questionable is whether the min/max X and Y values should be required or part of options (one could make a reasonable argument both ways).

       Chart::Options options;
       options
         .set_min_x(-10)
         .set_max_x(100)
         .set_min_y(-1000)
         .set_max_y(-70)
         .set_line_type(Chart::DASHED_LINES)
         .set_crosshairs_enabled(false);

       Chart chart(&f, options);
       chart.Render(&canvas);
    
Bad Examples

Complex

This is a bad example because there are very few constructor parameters (only two), all parameters are required for the object to make sense (although one could argue that "0" is a reasonable default for either parameter, it really makes more sense for this to be specified explicitly), and it is not confusing to simply supply the parameters to the constructor, directly.

       // This would be better as Complex c = new Complex(1.0, 3.0);
       Complex c = Complex.newBuilder().setReal(1.0).setImaginary(3.0).build();
    

Point

Point is a case where it doesn't make sense to use a builder for reasons similar to avoiding a builder with a Complex number representation. However, this particular example illustrates one other aspect... namely that builders are expected to operate on independent configuration; a builder that supports both polar and rectangular coordinates is probably not a great design (a better design would be to make polar and rectangular coordinates into completely separate classes with functions that make it easy to convert between the two explicitly, otherwise, one could easily waste computation inadvertently and unnecessarily as a result of accidentlly switching between the two representations).

      Point p =
         Point
           .newBuilder()
           .setX(0.5)
           .setTheta(7.8)
           .build();
    
Real Examples

Curious to see how this design pattern is used in the wild? While we make no claim regarding the "good-ness" or "bad-ness" of these uses, here are a handful of real examples to look at on GitHub that illlustrate this design pattern:

See also
Disclaimer

My statements are my own and do not necessarily reflect the opinions of Google Inc.