Builder pattern in TypeScript

builder-pattern-in-typescript

Introduction

The builder pattern is a creational pattern which allows to create complex objects in an easy way. You can create multiple representations of the objects step by step using the same construction code.

Applicability

  • you want to simplify the construction process of a complex object
  • different representations of the same object are required

Implementation

You can find the full example source code here.

In our example we are going to define a Builder which will create cars and motorcycles. The example has been simplified to focus on the pattern.

First, we are going to create a interface for the Builder which will define the different operations needed to create our object. Then, we will create a concrete builder which will implement the interface and will implement the operations.

It would be possible to have multiple concrete builders in case that we wanted to create different types of objects with the same construction process. For example, following our example we could have a CarBuilder and a MotorcycleBuilder.

As a last step, we will see how we can add a Director class where we can define the different construction processes so that they have to be defined only once. The Director is not necessary, you can always use the builder directly from the
client code.

Diagram of the builder pattern. Credit: Wikimedia Commons
Diagram of the builder pattern. Credit: Wikimedia Commons

1. Create the Builder interface and define the different steps needed to build the objects.

In our case, the Builder will create cars and motorcycles. The construction process will have only two steps: setSeats and setEngine. Reminder: this example has been simplified to focus on learning the pattern. In a real world example, the creation process would have more steps and it would be much more complex.

interface Builder {
  setSeats(seats: number): this;
  setEngine(engine: string): this;
}

2. Create a concrete builder which will implement the Builder interface and will implement the steps. Do this for each type of object you want to create.

In our example we are only going to have two concrete Builders, the CarBuilder and the MotorcycleBuilder.

class CarBuilder implements Builder {
  private car: Car;

  constructor() {
    this.car = new Car();
  }

  public setSeats(seats: number): this {
    this.car.setSeats(seats);
    return this;
  }

  public setEngine(engine: string): this {
    this.car.setEngine(engine);
    return this;
  }

  public getResult(): Car {
    return this.car;
  }
}

class MotorcycleBuilder implements Builder {
  private motorcycle: Motorcycle;

  constructor() {
    this.motorcycle = new Motorcycle();
  }

  public setSeats(seats: number): this {
    this.motorcycle.setSeats(seats);
    return this;
  }

  public setEngine(engine: string): this {
    this.motorcycle.setEngine(engine);
    return this;
  }

  public getResult(): Motorcycle {
    return this.motorcycle;
  }
}

Just with this code it would be enough to implement the Builder pattern. We could create a Car or a Motorcycle using the CarBuilder or the MotorcycleBuilder directly from the client code.

const car = new CarBuilder().setSeats(2).setEngine("V-12").getResult();

const motorcycle = new MotorcycleBuilder()
  .setSeats(2)
  .setEngine("V-4")
  .getResult();

3. We are going to go one step further and create the Director class. The Director will contain the different construction processes of the object representations that we need in our app.

In our example we are going to define 2 construction processes for the car and 2 for the motorcycle.

class Director {
  public buildFerrari(): Car {
    return new CarBuilder().setSeats(2).setEngine("V-12").getResult();
  }

  public buildToyota(): Car {
    return new CarBuilder().setSeats(7).setEngine("V-6").getResult();
  }

  public buildHonda(): Motorcycle {
    return new MotorcycleBuilder().setSeats(2).setEngine("V-4").getResult();
  }

  public buildYamaha(): Motorcycle {
    return new MotorcycleBuilder().setSeats(1).setEngine("V-2").getResult();
  }
}

And the Director can be used like this:

const director = new Director();
director.buildFerrari();
director.buildToyota();

director.buildHonda();
director.buildYamaha();

Advantages

  • You can create complex objects step by step in an easy way.
  • The same construction code can be reused to create different representations of objects.
  • Single Responsibility Principle: Complex construction processes can be isolated and separated from the object’s representation.

As always, make sure it makes sense to use this pattern in your application.
Otherwise you could be introducing unnecessary complexity.

Resources

Total
0
Shares
Leave a Reply

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

Previous Post
i-made-1,000x-faster-typescript-validator-library

I made 1,000x faster TypeScript Validator Library

Next Post
which-buzzwords-most-annoy-you?

Which buzzwords most annoy you?

Related Posts
arkui-x平台差异化

ArkUI-X平台差异化

跨平台使用场景是一套ArkTS代码运行在多个终端设备上,如Android、iOS、OpenHarmony(含基于OpenHarmony发行的商业版,如HarmonyOS Next)。当不同平台业务逻辑不同,或使用了不支持跨平台的API,就需要根据平台不同进行一定代码差异化适配。当前仅支持在代码运行态进行差异化,接下来详细介绍场景及如何差异化适配。 使用场景 平台差异化适用于以下两种典型场景: 1.自身业务逻辑不同平台本来就有差异; 2.在OpenHarmony上调用了不支持跨平台的API,这就需要在OpenHarmony上仍然调用对应API,其他平台通过Bridge桥接机制进行差异化处理; 判断平台类型 可以通过let osName: string = deviceInfo.osFullName;获取对应OS名字,该接口已支持跨平台,不同平台上其返回值如下: OpenHarmony上,osName等于OpenHarmony-XXX Android上,osName等于Android XXX iOS上,osName等于iOS XXX 示例如下:…
Read More