Understanding Builder Design Pattern made Easy

In this detailed article, let's understand what is a Builder pattern, how to implement it and compare with Abstract Factory

Why do you need a Builder?

Imagine a bike building application that specializes in developing bikes of various categories like Sports, Cruisers or Regular bikes and of different shapes and specifications.

The Bike to be developed is a complex entity with a large constructor that sets all of its specs at one go as below

public class Bike
{
    public int EngineCapacity { get; }
    public int Seats { get; }
    public int FuelCapacity { get; }
    public string ModelName { get; }
    public string SeriesName { get; }
    public string BikeCategory { get; }


    public Bike(
        string modelName,
        string seriesName,
        string category,
        int fuelCapacity,
        int engineCapacity,
        int seats
    )
    {
        this.ModelName = modelName;
        this.SeriesName = seriesName;
        this.Seats = seats;
        this.BikeCategory = category;
        this.EngineCapacity = engineCapacity;
        this.FuelCapacity = fuelCapacity;
    }
}

To make a bike, one must fill in all of these specs into the constructor. Also, when this bike needs to be transformed to fit into various types offered by the application (Sports, Cruiser or Regular) not all these values might need to be fed to the entity.

This creates a lack of flexibility and tight coupling with the object creation to its representation.

Builder pattern solves this by separating the instantiation logic from its representation.

The client where the Bike needs to be instantiated need not call a new keyword on the Bike and instead make use of a “Builder” that gets the job done.

What is Builder Pattern?

Builder pattern is one of the 23 Gang of Four (GoF) design patterns.

It is a Creational Pattern.

It solves the issue of creating a complex object which should result in different representations (variations) based on the requirements.

What is a Builder?

A Builder is a class which is responsible for creating a particular variation of an entity.

A client requests a Builder for an instance of given specs and the builder encapsulates the instantiation for that entity.

A builder also offers customization of the entity; the client can be able to set only those specs which are needed while ignoring the other.

The builder assembles all the specs of the entity based on the client requirements and returns a complex object that represents the particular representation of the entity that the client requires.

Invoking Builder using a Director

Sometimes a Client doesn’t invoke a Builder directly; it instead makes use of a Director that internally invokes a builder to assemble a particular representation of the entity and offers the Client an abstraction from the builder.

Using a Director is optional and depends on the application design. It creates a sort of simplicity on the Client part while abstracting the Builder invocation from the client.

How to Implement Builder Pattern with C# Example

For the above Bike entity, let’s say we would want to create an object representing a SportsBike, a Cruiser and a regular Moped.

For this instead of creating three instances by passing the values to the lengthy constructor, we can create a BikeBuilder class that looks after the building logic, which is as below.

#region Builder


public interface IBikeBuilder
{
    void SetEngineCapacity(int capacity);
    void SetSeats(int seats);
    void SetFuelCapacity(int fuelCapacity);
    void SetModelName(string modelName);
    void SetSeriesName(string seriesName);
    void SetBikeCategory(string bikeCapacity);
    Bike Make();
}


public class BikeBuilder : IBikeBuilder
{
    private string _bikeCategory;
    private int _engineCapacity;
    private int _fuelCapacity;
    private string _modelName;
    private int _seats;
    private string _seriesName;


    // build a Bike object based on
    // the customization and specs
    public Bike Make()
    {
        return new Bike(
            _modelName,
            _seriesName,
            _bikeCategory,
            _fuelCapacity,
            _engineCapacity,
            _seats
        );
    }


    public void SetBikeCategory(string bikeCapacity)
    {
        _bikeCategory = bikeCapacity;
    }


    public void SetEngineCapacity(int capacity)
    {
        _engineCapacity = capacity;
    }


    public void SetFuelCapacity(int fuelCapacity)
    {
        _fuelCapacity = fuelCapacity;
    }


    public void SetModelName(string modelName)
    {
        _modelName = modelName;
    }


    public void SetSeats(int seats)
    {
        _seats = seats;
    }


    public void SetSeriesName(string seriesName)
    {
        _seriesName = seriesName;
    }
}


#endregion

The BikeBuilder completely encapsulates the instantiation logic for the Bike object and also exposes setter methods for each specification of the Bike instance.

To create a Bike, we can call what all the specs we would want on our Bike and the Builder assembles all these specs onto the resulting Bike object when the Make() method is called.

To further simplify, we can add a Director who is responsible for invoking this builder for a tailor made experience.

The Director looks like below.

#region Build Director


public interface IBikeBuildDirector
{
    Bike MakeAvenger();
    Bike MakeChetak();
    Bike MakeR1();
}


public class BikeBuildDirector : IBikeBuildDirector
{
    private readonly IBikeBuilder _builder;


    public BikeBuildDirector(IBikeBuilder builder)
    {
        _builder = builder;
    }


    public Bike MakeR1()
    {
        _builder.SetBikeCategory(BikeType.Sports);
        _builder.SetEngineCapacity(1000);
        _builder.SetFuelCapacity(20);
        _builder.SetModelName("Yamaha R1");
        _builder.SetSeats(1);
        _builder.SetSeriesName("R Series");


        return _builder.Make();
    }


    public Bike MakeChetak()
    {
        _builder.SetBikeCategory(BikeType.Regular);
        _builder.SetEngineCapacity(80);
        _builder.SetFuelCapacity(15);
        _builder.SetModelName("Chetak");
        _builder.SetSeriesName("Chetak");
        _builder.SetSeats(2);


        return _builder.Make();
    }


    public Bike MakeAvenger()
    {
        _builder.SetBikeCategory(BikeType.Cruiser);
        _builder.SetEngineCapacity(220);
        _builder.SetFuelCapacity(15);
        _builder.SetModelName("Avenger 220");
        _builder.SetSeriesName("Avenger");
        _builder.SetSeats(2);


        return _builder.Make();
    }
}


#endregion

The Client creates an instance of the Director and supplies an instance of the Builder it wishes to use.

The Director calls the Make() method on the supplied builder which returns the customized and assembled Bike.

#region CallingClient


public class BuilderClient
{
    public void CallBuilder()
    {
        IBikeBuilder builder = new BikeBuilder();
        IBikeBuildDirector director = new BikeBuildDirector(builder);


        //build an R1
        Bike r1 = director.MakeR1();
        Describe(r1);


        //build an Avenger
        Bike avenger = director.MakeAvenger();
        Describe(avenger);


        //build a chetak
        Bike chetak = director.MakeChetak();
        Describe(chetak);
    }


    private void Describe(Bike b)
    {
        foreach (System.Reflection.PropertyInfo p in b.GetType().GetProperties())
        {
            Console.WriteLine(p.Name + ":" + p.GetValue(b, null));
        }
        Console.WriteLine("\n");
    }
}


#endregion

Calling the BuilderClient above would result in the below output.

Similarities between Builder and Abstract Factory

Builder pattern has a few similarities with the Abstract Factory, which is also a creational pattern aimed at instantiating a specific entity from a family of factory types.

Generally, when we need to create different representations of a single entity based on the scenarios, we would first start by creating a Factory Method, that returns a specific object based on the input criteria.

As the application grows in size, we move from a simple Factory to a Builder which offers an assembled instance or an Abstract Factory that returns an instance of a fixed specification.

Difference between Abstract Factory and Builder Design Pattern

  • In Abstract Factory we have multiple concrete types of a single abstract type and an object returned from an Abstract Factory can be one among many concrete implementations of that abstract type.
  • Whereas in a Builder, all the time the application works on only a single complex type but the specs of that type varies with the requirements.
  • Since Builder offers a series of assembling methods on top of the object to be created, it is slower than an Abstract Factory.
Abstract FactoryBuilder
Creational PatternCreational Pattern
returns an instance of a fixed specification based on the conditionoffers an assembled instance
we have multiple concrete types of a single abstract typeall the time the application works on only a single complex type
object returned can be one among many concrete implementations of that abstract typespecifications of the returned type instance varies with the requirements
Faster than Builder, since there is no customization on top of a created instanceoffers a series of assembling methods on top of the object to be created, slower than an Abstract Factory
differences between abstract factory and builder

Advantages of using Builder Pattern

Using a Builder can provide us with certain advantages as below –

  • Varying the entity’s representation based on the requirements
  • Encapsulates the entity object creation logic
  • Provides customization on how the entity object is created by means of a step-by-step assembly

Things to keep in Mind while using Builder Pattern

  • A varying builder implies building a new Builder implementation each time a product varies.
  • In our case, we handled it with a Director that takes care of the variation on top of a Generic BikeBuilder approach; it might not be possible all the time.
  • The complexity of the code increases since we’re introducing several levels (Builder, Director) between an Entity and a Client.

Buy Me A Coffee

Found this article helpful? Please consider supporting!

Ram
Ram

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity. You can connect with me on Medium, Twitter or LinkedIn.

Leave a Reply

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