January 18, 2021

Discussing the Builder Pattern – a simple example

This article provides a simple discussion of the Builder pattern. It also contains simple example implementations of the Builder pattern.
After reading, you will be able to answer questions like: What kind of problems does the Builder pattern solve? How does it compare to other design patterns of its kind? How is it implemented?

Table of contents

What is the Builder pattern?

As the name suggests, the Builder pattern is one of the creational design patterns. Patterns of this category solve problems related to complex object creation.
In other words, the Builder pattern defines a builder class that produces a result instance (product) by building/configuring this instance step by step. This is contrary to creating an instance in a single step, for example when using the constructor or a factory.

Usually, the Builder pattern describes a product class (as all creational patterns do), a builder class and a client class, that uses the builder class.
Such a builder class defines a set of methods, to store data or state related to the product instance, and a finalizer method, for example Build(), that actually instantiates the product and then transfers the collected data to this instance, before returning the new instance to the client of the builder class.


The Builder is competing with other creational patterns like Abstract Factory, Factory Method or Singleton and Prototype (cloning). Depending on the context, the Builder pattern can solve problems that other creational patterns can’t.

When to use the Builder pattern?

1. Multiple instance representations of the same type

For example, when you have different instance representations of an object, you can chose the Builder pattern to encapsulate the complex instantiation.
A Person class can represent a user, a employee or manager, a customer. Each instance requires a special construction or constructor parameters to give the Person instance the specialized representation. Specialized classes like UserBuilder or CustomerBuilder can hide those special construction of a Person instance and help to simplify the code and improve the readability.

2. Deferred instantiation or step-by-step configuration

As you may have noticed, the previous problem alone can also be solved by implementing the Abstract Factory pattern. But when the creation or configuration of the instance is deferred or must take place in steps, for instance, when the required parameters are not all known or available at the same time, the Builder can help to collect and store the required parameters until the final creation of the result instance.

3. Manage allocation of required resources to improve efficiency and performance

Deferred instantiation is especially useful, when the created instance uses valuable resources. Certainly, you would try to avoid to allocate such resources until the instance is actually really needed. Therefore, you would try to defer the instantiation.
Still, the Builder allows the client to operate on or configure the deferred instance, by mimicking it’s state.

How StringBuilder uses Builder to improve efficiency of string manipulations

A very famous example of the Builder pattern is the StringBuilder class, that exists in Java and C#. You should know, that in both languages strings are implemented immutable. This means, they can’t be modified after their creation. To modify a string value, this languages require the compiler to create a new string instance. For each modification. This means, a simple modification like concatenating a number of strings will create multiple objects in memory, which can be quite expensive. Additionally, resource allocations are usually also time consuming. Not to forget, there is a garbage collector running in the background. Giving the GC this extra work to collect all those temporary string instances will also impact the application’s overall performance.
In case of heavy string manipulations like replace operations, all those costs can drastically impact the performance of your algorithm. Note, that because text search alone does not require to manipulate the string, StringBuilder does not provide an API to search. The focus is on string manipulations.

string name = "Walter";

// Creates a new instance of 'name' allocating additional memory
name = name + "White"; 

string alphabet = string.Empty;
for (int decimalAsciiCode = 65; decimalAsciiCode < 91; decimalAsciiCode++)
{
  // Creates a new instance of 'alphabet',
  // allocating additional memory on each iteration
  alphabet = string.Concat(alphabet, (char) decimalAsciiCode);
}

Other than expected, the above code will not modify the local name or alphabet variables. But instead, it will throw away the original variables and allocate new memory for the result of each string modification. The performance penalty is especially high, when the string modifications take place repeatedly like in an iteration.
To solve this problem of excessive memory usage and costly instantiation time due to heavy string manipulations, we can use the StringBuilder, which offers a mutable string representation and therefore helps to manage the memory resource more efficiently. After the manipulations are completed, we can request the StringBuilder to return the final string instance by invoking the finalizer method Build().
You can see, that the Builder can be much more than just a simple instance creator.

Another example how Builder can improve the resource management

While the string is only expensive when manipulated excessively, there are types that are expensive due to the resources they internally allocate.

For example, consider a type that allocates network resources in its constructor. To execute a user initiated operation e.g, send a file across the web, we need to create an instance of this type. Additionally the user is allowed to cancel this operation, while we are configuring the network resource. We can chose the Builder to defer related allocations until we decide to produce the product based on the collected parameters. In other words, we collect the settings (state) of the future product instance instead of manipulating/configuring the real instance (product). And when instantiating the product, we apply the buffered state to the final instance.

I will provide an implementation of this Builder example later.

Builder is not meant to mutate immutable types

In this section, I would like to point out that the Builder is not aiming to mutate immutable types. It appears to be widely spread, that the purpose of the Builder is to allow mutation of immutable types or to hide big constructor calls to make your code more readable. This is not true.
This are rather side effects, than primary goals.

Furthermore, immutable means an instance can’t change it’s values after instantiation. You will always have to create a new instance in order to set new values. That’s the deal.
There is no pattern in the world that can break immutability.
Immutable types are a compiler concept. Immutable types simply make use of compiler features, like private members and public constructors.

Getting the StringBuilder example straight

As you can see in the previous StringBuilder example, the Builder helps to reduce the memory footprint. The fact that it operates on an immutable type (in this case string) is generally not of any relevance, although in case of  string, immutability is the reason for the string to have the negative performance impact. But immutability is not a concern of the Builder.

After reading the section of Manage allocation of required resources to improve efficiency and performance, we know that the string is an expensive type: it’s immutable and therefore modifying a string requires to create copies in memory. Additionally, those copies will keep the garbage collector busy.
This is a fact for both managed languages Java and C#.
It is the only reason why the Java and the .NET team chose to use the Builder pattern to provide a more efficient API to manipulate a string virtually. Virtually means, we can concatenate (append) strings, we can insert, replace, compare or randomly access the virtual string. Only when calling the Build() method, a true string is created from the buffered values.

We can view StringBuilder as a mutable alternative to the immutable string.

This should make clear, that the Builder pattern implemented by the StringBuilder is following the intention to make string manipulations more efficient. The StringBuilder is a perfect example to show how to control resources by deferring instantiation of expensive types.

Above all, if all we are interested in is to avoid the invocation of big constructors, which would in fact improve the readability, we would prefer other design patterns like Abstract Factory or Dependency Injection. Both offer a pattern to encapsulate and hide constructor details from client code and are far simpler to implement. When we compare those two patterns to the Builder, it should become evident where the strengths of the Builder are: what neither Abstract Factory nor Dependency Injection can do, is to split up type instantiation.

The Builder pattern in comparison

When discussing the Builder pattern, we should also spend some time to compare it to other patterns of the same kind. This will help us to understand the true power of the Builder and therefore will also help to decide when to use it.

As already mentioned before, the Builder belongs to the group of creational design patterns. Its competitors are the at least equally famous Abstract Factory, Factory Method, Singleton and Prototype.

Obviously, creating instances is a common concern. But when it comes to how those instances are created each pattern has its own focus.
Abstract Factory hides constructor details of the concrete product type.
Factory Method enables a subclass to control instantiation of types used in the superclass.
Prototype creates instances by cloning a prototype instance.
Singleton ensures that only a single instance of a type is created.

We can see, what all have in common, is that they encapsulate the instantiation and that we have to request (and therefore create) the instance before we can configure it. This is exactly where the Builder excels. The Builder also creates instances, but in contrast it allows us to configure the instance in advance. The configuration can be split up into different steps and can be changed until the actual instance is finally requested. The Builder achieves this by allowing the encapsulating type to “buffer” the state or collect the values of the future instance.

Summing it up, Builder allows to virtually work on the instance and to define its state, before it is created.

Implementing the pattern

The Builder pattern usually involves an abstract type like an interface or abstract class. This type describes the template for the specialized builders, a hierarchy of IBuilder implementations.

The following example shows how to use the Builder to solve the problem of Multiple instance representations of the same type and Deferred instantiation.
It involves a class Person, which can represent a team leader or a team member. The team leader aggregates an arbitrary number of team members, while the team member has no aggregations.

We also have the IBuilder interface and its TeamLeaderBuilder and TeamMemberBuilder implementations, that take care to produce the proper representation. The IBuilder.Build() method actually creates the Person instance (deferred creation).

The UML diagram

A very simple three steps example implementation in C#

Step 1: define the product of the Builder class
// A Person can be a team leader who has associated team members who he manages,
// or a Person can be a normal team member, without any associated team members
class Person
{
  // Constructor
  public Person(string name) => this.Name = name;

  public List<Person> AssociatedPersons { get; }
  public string Name { get; set; }
}
Step 2: create the template interface for the specialized Builder implementations
interface IPersonBuilder
{
  public Person Build();
}

interface ITeamMemberBuilder : IPersonBuilder
{
  public void SetName(string name);
}

// A team leader is a special team member. Both are a person.
interface ITeamLeaderBuilder : ITeamMemberBuilder, IPersonBuilder
{
  public void AddMember(Person teamMember);
}
Step 3: implement the specialized TeamMemberBuilder and TeamLeaderBuilder
// A normal team member has no associated persons to manage.
// Therefore the product's Persons collection will remain empty (no AddMember() method in this builder), 
// but is still initialized to avoid null reference exceptions
class TeamMemberBuilder : ITeamMemberBuilder
{
  private string Name { get; set; }

  // A normal team member has no associated persons to manage.
  public TeamLeaderBuilder() => this.Persons = new List<Person>();

  public void SetName(string name) => this.Name = name;

  public Person Build() 
    => new Person(this.Name) { Persons = new List<Person>() };
}

class TeamLeaderBuilder : ITeamLeaderBuilder
{
  private List<Person> Members { get; }
  private string Name { get; set; }

  public TeamLeaderBuilder() => this.Members = new List<Person>();

  public void SetName(string name) => this.Name = name;
  public void AddMember(Person teamMember) 
    => this.Members.Add(teamMember);

  public Person Build() 
    => new Person(this.Name) { Persons = this.Members };
}
Usage example
public static void Main()
{
  // Create the builder instances
  var teamLeaderBuilder = new TeamLeaderBuilder();
  var teamMemberBuilder = new TeamMemberBuilder();

  // Collect and store data required for instance creation 
  // step by step
  teamLeaderBuilder.SetName("Walter White");

  teamMemberBuilder.SetName("Jesse Pinkman");

  // Finally create a specialized instance of Person deferred
  Person teamMember = teamMemberBuilder.Build();

  teamLeaderBuilder.AddMember(teamMember);

  // Complete collecting data. 
  // Finally create a specialized instance of Person deferred
  Person teamLeader = teamLeaderBuilder.Build();
}

An extended version using method chaining

The idea of method chaining is to avoid intermediate local variables. Instead you chain methods, where each method operates on the return value of the previous method call.
There are scenarios, where making such temporary local variables redundant, can improve the readability of the code.
LINQ is a perfect example, where each method returns a IEnumerable as result value, that allows to invoke another Enumerable method:

var numbers = new List<int> { 2, 3, 5, 7, 11, 13, 17 };

// Instead of storing each temporary value in a local variable,
// the API of Enumerable allows us to chain the method invocations
int sum = numbers.Select(number => number + 2)
  .Where(number => number % 5 == 0)
  .Select(number => number * 2)
  .Sum();

Another example: a HTTP client, based on the Builder using method chaining to configure the connection

Problem:
a) Firstly, we need to dispatch a HTTP request and have to use the HttpClient, which uses unmanaged system resources. We don’t want to waste resources. Therefore, we only want to create an instance of HttpClient when really needed. We somehow have to realize a deferred instantiation of the HttpClient.
b) Secondly, the user can cancel the HTTP request at any time during the configuration of the request.
c) And finally, we need an API that allows to configure the request step by step while we are collecting the required data.

Solution: Builder pattern. 

Following the pattern, we will implement a HttpRequestBuilder. But instead of the Build() method, we will have a SendAsync() method. Basically,  the finalizer method is renamed from  Build() to SendAsync() and does not return a product, but some result value. The instance of interest will be the HttpClient, which is a private member of the HttpRequestBuilder, as it will only be used internally.

The HttpRequestBuilder will only instantiate the HttpClient on request. This way the HTTP connection can be configured step by step without creating the expensive HttpClient. We can cancel the operation at any time until the final call to the SendAsync() method.
Although we don’t have the classic Build() method, we can still clearly identify the characteristics of the Builder.

In our example, the HttpRequestBuilder will take full control over allocated resources. This means, the HttpRequestBuilder is responsible to control the lifetime of the underlying HttpClient and to call its Dispose() method to free the expensive unmanaged resources.

Usage example

var httpRequestBuilder = new HttpRequestBuilder();

// To show method chaining, all methods are called in a chained manner.
// In a real world scenario, you'd may split the individual steps where required
HttpResponseMessage response = await httpRequestBuilder.SetUrl("https://bioniccodeblog.com")
  .SetAuthorizationHeader("Basic YWxhZGRpbjpvcGVuc2VzYW1l")Transfer-Encoding
  .SetTransferEncodingHeader("gzip, chunked")
  .SetHostHeader("developer.cdn.mozilla.net")
  .SetContentTypeHeader("application/json")
  .SetRequestContent("{isJson: true}")
  .SendAsync();

Implementing the HttpRequestBuilder

To allow method chaining, each method of the HttpRequestBuilder will return a reference to itself:

class HttpRequestBuilder
{
  private string TransferEncoding { get; set; }
  private string ContentType { get; set; }
  private string Host { get; set; }
  private string Url { get; set; }
  private string Authorization { get; set; }
  private object Content { get; set; }

  public HttpRequestBuilder SetUrl(string url)
  {
    this.Url = url;

    // Enable optional method chaining
    return this;
  }

  public HttpRequestBuilder SetContentType(string contentType)
  {
    this.ContentType = contentType;

    // Enable optional method chaining
    return this;
  }

  public HttpRequestBuilder SetHost(string host)
  {
    this.Host = host;

    // Enable optional method chaining
    return this;
  }

  public HttpRequestBuilder SetAuthorization(string authorization)
  {
    this.Authorization = authorization;

    // Enable optional method chaining
    return this;
  }

  public HttpRequestBuilder SetContent(string content)
  {
    this.Content = content;

    // Enable optional method chaining
    return this;
  }

  public HttpRequestBuilder SetTransferEncoding(string transferEncoding)
  {
    this.TransferEncoding = transferEncoding;

    // Enable optional method chaining
    return this;
  }

  public async Task<HttpResponseMessage> SendAsync()
  {
    // Free unmanaged resources after leaving current scope block
    using var httpClient = new HttpClient();

    httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(this.ContentType));
    httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(this.ContentType));
    httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(this.Authentication.Split(' ', StringSplitOptions.TrimEntries));
    httpClient.DefaultRequestHeaders.Host = this.Host;
    this.TransferEncoding.Split(',', tringSplitOptions.TrimEntries)
      .ToList()
      .ForEach(encoding => httpClient.DefaultRequestHeaders.TransferEncoding.Add(new TransferCodingHeaderValue(encoding)));

    var requestBody = new HttpRequestMessage(HttpMethod.Post, "relativeAddress"){Content = new StringContent(this.Content)};    
     HttpResponseMessage response = await httpClient.SendAsync(this.Url, requestBody);

    return response;
  }

If you have found a spelling error, please, notify us by selecting that text and pressing Ctrl+Enter.

Leave a Reply

Your email address will not be published.

Spelling error report

The following text will be sent to our editors: