Design Patterns Software Engineering

Design Patterns – Builder

Pinterest LinkedIn Tumblr
The code is available on GitHub here

The builder pattern breaks the creational process of an object in multiple steps. This enables us to create different representations of the same object, using different steps and passing different parameters each time. Also, we don’t need to call all of the steps in order to get the desired final product.

Builder pattern a deep dive

A representation of an object is an object instance which is different in some aspects from the other objects of the same type. For example, you can build an RC car (Remote Controlled car) with various types of wheels. Those different types of wheels, result to a different representation of the RC Car object. The builder pattern helps us to extract separate steps for the RC car creation process, making which type of wheel the final product will use one of the steps.

This would have multiple benefits in our code. First, we encapsulate the creation of a complex object, making the client code more agnostic of the internal representation of the object. Also, because the client is using the builder’s abstract interface we can change implementations of the final product without making the rest of the application to change. However, the client must have more domain knowledge about how to use the construction steps in comparison with the other factory patterns which requires almost none.

Creating a builder for different RC cars

In this section, we examine how we can implement an RC car builder in order to create different representations of an RC car. Our builder will be capable of creating RC cars with different types of wheels (Simple wheel and Omnidirectional), different type of boards (Arduino and UDOO) and different number of motors (for driving the wheels). Combining the different steps of the creational process and providing them with the proper implementations/parameters each time, we will be able to build three different types of RC Cars:

  • Ackermann – is the standard car which uses the front wheels for steering and the back for drive.
  • Omnidirectional – which has special wheels and it steers depending on which wheels are moving and in which direction. All wheels are separately driven by motors.
  • Differential – this type of car has one motor driving the wheels in one side and another motor driving the wheels on the other. The car can steer depending on the difference of speed between its left and right side.

The RCCar class

Our RC car will contain an electronic board, a number of wheels and a number of motors in order to move around. We keep the class itself simple and override the ToString() method in order to print later the internal configuration of the RCCar

public class RCCar
{
   public Board Board { get; }
   public List<Wheel> Wheels { get; }
   public int NumberOfMotors { get; }

   public RCCar(Board board, List<Wheel> wheels, int numberOfMotors)
   {
      Board = board;
      Wheels = wheels;
      NumberOfMotors = numberOfMotors;
   }

   public override string ToString()
   {
      return $"[Board:{Board}, Wheel Type:{Wheels.First()}, Number of wheels:{Wheels.Count()}, Number of motors:{NumberOfMotors}]";
   }
}

The WheelType is a simple Enum with two distinct values.

public enum WheelType 
{ 
   Simple,
   Omnidirectional
}

The Board is an abstract object and the appropriate implementation should be provided. The available boards are Arduino and UDOO.

public abstract class Board { }

public class Arduino : Board
{
   public override string ToString() => "Arduino";
}

public class UDOO : Board 
{
   public override string ToString() => "UDOO";
}

The IRCCarBuilder interface

Our builder will allow the client to add motors, type of wheels and the electronic board to be used. Its interface is shown below:

public interface IRCCarBuilder
{
   void AddBoard(Board board);
   void AddWheels(int count);
   void AddWheelType(WheelType wheelType);
   void AddMotors(int count);

   RCCar Build();
}

The client can use the various creational functions of the IRCCarBuilder to get the final RCCar object.

The RCCarBuilder implementation

The implementation of the builder interface simply stores the appropriate instance variable each time the client calls a creational step. Finally, when the client is finished configuring the building process the builder instantiates the desired object.

public class RCCarBuilder : IRCCarBuilder
{
   private List<Wheel> _wheels = new List<Wheel>();
   private int _numberOfMotors;
   private WheelType _wheelType;
   private Board _board;

   public void AddBoard(Board board)
      => _board = board;

   public void AddMotors(int count)
      => _numberOfMotors = count;

   public void AddWheelType(WheelType wheelType)
      => _wheelType = wheelType;
   
   public void AddWheels(int count)
   {
      _wheels.Clear();
      for (int i = 0; i < count; i++)
      {
         _wheels.Add(CreateWheel());
      }
   }

   private Wheel CreateWheel()
   {
      switch (_wheelType)
      {
         case WheelType.Simple:
            return new SimpleWheel();
         case WheelType.Omnidirectional:
            return new OmnidirectionalWheel();
      }

      throw new ArgumentException();
   }

   public RCCar Build()
      => new RCCar(_board, _wheels, _numberOfMotors);
}

In this example we instantiate and return the RCCar class which of course is a concrete class. However, in other cases the returned object could be an abstract class or an interface with the builder instantiating the proper implementation before returning it to the client. Either way, the builder has the same functionality.

Creating the different RCCars

Its time to put our builder to use. We will inject its interface into a client and use it in order to build different RC cars.

Creating an Ackerman RCCar

With one motor for drive, four simple wheels and an Arduino board, the client that creates an Ackermann RCCar is shown below:

public class Client
{
   private readonly IRCCarBuilder _builder;

   public Client(IRCCarBuilder builder)
   {
      _builder = builder;
   }

   public RCCar UseTheCar()
   {
      _builder.AddBoard(new Arduino());
      _builder.AddMotors(1);
      _builder.AddWheelType(WheelType.Simple);
      _builder.AddWheels(4);

      var rccar = _builder.Build();
      Console.WriteLine($"Using the {rccar} rccar");
   }
}

class Program
{
   public static void Main()
   {
      Client client = new Client(new RCCarBuilder());
      client.UseTheCar();
   }
}

After running the code, the result would be the following:

Using the [Board:Arduino, Wheel Type:Simple Wheel, Number of wheels:4, Number of motors:1] rccar

Make the Builder fluent

A more expressive approach that is common in builder pattern is using a fluent interface. Up until now we use the builder calling one method after another like below.

_builder.AddBoard(new Arduino());
_builder.AddMotors(1);
_builder.AddWheelType(WheelType.Simple);
_builder.AddWheels(4);

A fluent builder doesn’t need the instance _builder every time to call a method. Instead we chain our calls together creating a more expressive result.

_builder.AddBoard(new Arduino())
      .AddMotors(1)
      .AddWheelType(WheelType.Simple)
      .AddWheels(4)
      .Build();

Its certainly less code and perhaps more expressive, chaining the methods like that. The trick in order to achieve this kind of syntax is in every creation step instead of returning void we return the IRCCarBuilder itself. The new fluent interface is shown below.

public interface IRCCarBuilder
{
   IRCCarBuilder AddBoard(Board board);
   IRCCarBuilder AddWheels(int count);
   IRCCarBuilder AddWheelType(WheelType wheelType);
   IRCCarBuilder AddMotors(int count);

   RCCar Build();
}

Also, we must change the implementation and in every method we should return this instead of nothing.

public class RCCarBuilder : IRCCarBuilder
{
   ...
   public IRCCarBuilder AddBoard(Board board)
   {
      _board = board;
      return this;
   }

   public IRCCarBuilder AddMotors(int count)
   {
      _numberOfMotors = count;
      return this;
   }
   ...
   public RCCar Build()
   {
      return new RCCar(_board, _wheels, _numberOfMotors);
   }
}

Our builder is now transformed to a fluent builder!

The role of a Director in Builder

A Director encapsulates the steps needed to create a particular object. That means we decouple the client even further and instead of using the builder directly, the client can use the proper Director to get the final product. For example, in case we need to build an Ackermann RCCar, we can build an AckermannDirector that encapsulates the steps needed to create such an object and then provide this Director to every client that needs to create an Ackermann RCCar. That way we can achieve the granularity of the Builder with little domain knowledge from the clients about how to create a particular RCCar.

In order to use the Director we first must define an IDirector interface.

public interface IDirector
{
   RCCar Construct();
}

As we can see, this looks like a factory.

Creating an AckermannDirector

public class AckermannDirector : IDirector
{
   private readonly IRCCarBuilder _builder;

   public AckermannDirector(IRCCarBuilder builder)
   {
      _builder = builder;
   }

   public RCCar Construct()
      => _builder.AddBoard(new Arduino())
         .AddMotors(1)
         .AddWheelType(WheelType.Simple)
         .AddWheels(4)
         .Build();
}

Creating an OmnidirectionalDirector

An omnidirectional car has a motor for each of its wheel. Also the wheel type must be Omnidirectional.

public class OmnidirectionalDirector : IDirector
{
   private readonly IRCCarBuilder _builder;

   public OmnidirectionalDirector(IRCCarBuilder builder)
   {
      _builder = builder;
   }

   public RCCar Construct()
      => _builder.AddBoard(new UDOO())
         .AddMotors(4)
         .AddWheelType(WheelType.Omnidirectional)
         .AddWheels(4)
         .Build();
}

Creating a DifferentialDirector

A differential RC car has two motors driving each side of the car. Our particular implementation consists of 6 simple wheels (3 wheels for each side of the RC car).

public class DifferentialDirector : IDirector
{
   private readonly IRCCarBuilder _builder;

   public DifferentialDirector(IRCCarBuilder builder)
   {
      _builder = builder;
   }

   public RCCar Construct()
      => _builder.AddBoard(new UDOO())
         .AddMotors(2)
         .AddWheelType(WheelType.Simple)
         .AddWheels(6)
         .Build();
}

Example usage

Its time to use the directors we build. We make our client to take an IDirector interface and call its Construct() method to get the proper RCCar object.

public static void Main()
{
   var builder = new RCCarBuilder();
   IDirector director = new AckermanDirector(builder);

   var client = new Client(director);
   client.UseTheCar();

   director = new OmnidirectionalDirector(builder);

   client = new Client(director);
   client.UseTheCar();

   director = new DifferentialDirector(builder);

   client = new Client(director);
   client.UseTheCar();
}

public class Client 
{
   private readonly IDirector _director;

   public Client(IDirector director)
   {
      _director = director;
   }

   public void UseTheCar()
   {
      var rccar = _director.Construct();
      Console.WriteLine($"Using the {rccar} rccar");
   }
}

The result of the above code is shown below.

Using the [Board:Arduino, Wheel Type:Simple Wheel, Number of wheels:4, Number of motors:1] rccar
Using the [Board:UDOO, Wheel Type:Omnidirectional Wheel, Number of wheels:4, Number of motors:4] rccar
Using the [Board:UDOO, Wheel Type:Simple Wheel, Number of wheels:6, Number of motors:2] rccar

Recommendations

If you want to learn more, refresh or better understand design patterns and software design in general, I also recommend reading the following books:

A must in Design Patterns

A very well written book with plenty of examples and insights

Write A Comment

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.