DEV Community

loading...
Cover image for JavaScript Design Patterns - Builder

JavaScript Design Patterns - Builder

Coderslang: Become a Software Engineer
Teaching you to code at js.coderslang.com - JavaScript, HTML, CSS, Node.js, React.js, React Native
Originally published at learn.coderslang.com ・5 min read

Builder is one of the Creational Design Patterns, which help you deal with how objects are created. You should pay close attention to your object creation techniques that may morph into serious design problems if you neglect them.

Start here if you're just getting started with Design Patterns in JavaScript

Real-world examples

Picture having to assemble tiny lego pieces into what looks like a house. There are several different ways of grouping them into various representations.

A basic house will have a door, zero or more windows, and a roof with surrounding walls. Some may even have a garage, and some have a swimming pool. We'd wish for the process to be simpler and flexible to accommodate changes.

A step-by-step assembly of the lego pieces at your disposal forms the essence of the Builder pattern.

Objects are thus constructed following a sequence of steps one after the other instead of arriving at it all at once.

JavaScript objects are a collection of properties and methods. Properties are fields or variables associated with the object. And methods are functions that you're going to invoke to manipulate the fields. Objects' properties are closely related to each other, at least semantically in the worst case.

JavaScript provides many ways to create objects.

Object Initializer

The most common method of creating an object in JavaScript is an object literal notation.

const employee = {
  firstName: 'Sam',
  lastName: 'Greene',
  id: '12340987',
  phone: '07123456789'
};
Enter fullscreen mode Exit fullscreen mode

where firstName, lastName, id and phone are properties of the object named employee.

Constructor

Alternatively, we can use a constructor to create multiple instances of similar objects using the new keyword.

class Employee {
    constructor(fname, lname, id, ph) {
      this.firstName = fname;
    this.lastName = lname;
    this.id = id;
    this.ph;
  }
};

const employee1 = new Employee('Sam', 'Greene', 12340987, '07123456789');
const employee2 = new Employee('Nate', 'Tyson', 56478390, '07987654321');
Enter fullscreen mode Exit fullscreen mode

Using Object.create

Object.create() is an inbuilt function that you can use to create multiple instances of an object. It allows you to pass in the template object as an input, thus choosing the prototype for the objects as desired, without a constructor.

const Employee = {
  isAdmin: false,
  getRole: function() {
      return this.isAdmin ? 'Admin' : 'RegularEmp';
  };
};

const emp1 = Object.create(Employee);
emp1.getRole(); //'RegularEmp'

const emp2 = Object.create(Employee);
emp2.isAdmin = true;
emp2.getRole(); //'Admin'
Enter fullscreen mode Exit fullscreen mode

In this example, both emp1 and emp2 inherited the method getRole. Once you've set the isAdmin to true, the role of emp2 changed to Admin.

The problem

So it looks like there's already a bunch of ways to create objects in JavaScript. Why would we want to invent something different?

Let's consider two important questions:

  • What if you need to create a complex object comprising too many fields and properties?
  • What if you need to create multiple instances of almost the same object?

Object literal is not a good choice for it doesn't help with code reusability. Every time you need a new object, you'd have to list all its fields over and over.

Constructor would solve the issue to a certain extent, but it'd be cumbersome. You'd have to remember the inputs to it, some of which are mandatory and others aren't.

The Builder Pattern

What if you had a way to address just the above two problems while hiding the internal representation from anyone using it?

Objects are all around us in real life. And you can compare JavaScript objects with them.

A car, for example, comes in different colors or with a different number of seats. A house can have a various number of doors, windows, and chimneys. Differences are everywhere, yet there are some similarities.

The Builder Pattern gives you a way to simplify creating a complex object by separating it from its representation.

This pattern eases the construction of an object, taking the process through a step-by-step progression while also encapsulating (hiding) the implementation details of these steps.

Let's look into what a flexible design looks like for a simple OTG builder and how it evolves with the help of the Builder pattern.

Building an OTG using the Builder Design Pattern

There should be provision to build OTGs in various colors, models, pricing, and features. Some are built to support high temperature and a good timer selection span. For example, lower-end models come with low-temperature settings and a default timer.

Step 1: Class OTG

A simple class for OTG here has a JS constructor that takes in four parameters. Every OTG instance has a default title, an overridable temperature, and time selections which are by default set to 150 and 30 respectively.

class OTG {
    constructor(model, color, maxTemperature, maxTimeSelection) {
      this.model = model;
      this.title = 'OTG';
      this.color = color;
      this.maxTemperature = maxTemperature || 150;
      this.maxTimeSelection = maxTimeSelection || 30;
    }
}

const redOTG = new OTG('LG', 'red');
const highTempOTG = new OTG('LG', 'black', 200);
const highendTimeOTG = new OTG('LG', 'red', '150', '60');
Enter fullscreen mode Exit fullscreen mode

The above code looks ok for now. But there's a problem with it.

Imagine having to create multiple selections of OTG instances. It will get difficult to keep track of the parameters and their order in the constructor. Also, at times, there will be no need for sending in certain optional parameters.

Step 2: Create a builder class

Let's look at how we can avoid cramming the constructor to accept minimal parameters.

class OTGBuilder {
  constructor(model, color) {
    this.model = model;
    this.title = 'OTG';
    this.color = color;
  }
}
Enter fullscreen mode Exit fullscreen mode

OTGBuilder class here is responsible for the creation of instances of the OTG class. But it doesn't do this yet. For now, it just captures the basic fields model and color.

These fields are a must, so we include them directly in the constructor.

Step 3: Add optional parameters as separate functions

You've probably noticed that the fields maxTemperature and maxTimeSelection can't be configured with a the constructor.

These properties are optional and we'll create separate functions to set them.

class OTGBuilder {
  constructor(model, color) {
      this.model = model;
    this.title = 'OTG';
    this.color = color;
  }
  setMaxTemperature(temp) {
    this.maxTemperature = temp;
    return this;
  }

  setMaxTimeSelection(maxTime) {
    this.maxTimeSelection = maxTime;
    return this;
  }
}
Enter fullscreen mode Exit fullscreen mode

It's necessary to add return this; to make sure we can use chained call when we're working with the Builder Design Pattern.

const otg = new OTGBuilder('MorphyRichards', 'Black')
.setMaxTemperature(250)
.setMaxTimeSelection(60);
Enter fullscreen mode Exit fullscreen mode

Step 4: Build

The only remaining problem is that the object otg isn't the proper OTG yet. It's an instance of the OTGBuilder class. And you need to implement the last step of the process, which is the build() method.

class OTGBuilder {
  constructor(model, color) {
    this.model = model;
    this.title = 'OTG';
    this.color = color;
  }
  setMaxTemperature(temp) {
    this.maxTemperature = temp;
    return this;
  }

  setMaxTimeSelection(maxTime) {
    this.maxTimeSelection = maxTime;
    return this;
  }

  build() {
    return new OTG(this.model, this.color,
    this.maxTemperature, this.maxTimeSelection);
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, you can call the build() method when you're ready to create your OTG.

const basicOTG = new OTGBuilder('MorphyRichards', 'Black')
  .setMaxTemperature(250)
  .setMaxTimeSelection(60)
  .build();
Enter fullscreen mode Exit fullscreen mode

If you want to, you can always skip the steps to set optional parameters.

const default = new OTGBuilder('Generic OTG', 'White')
  .build();
Enter fullscreen mode Exit fullscreen mode

The properties maxTemperature and maxTimeSelection will default to 150 and 30 unless you overwrite them with setMaxTemperature and setMaxTimeSelection.

Thanks for reading!

I hope you liked this intro the Builder Design Pattern. Like/share/comment if you want me to cover more JavaScript Design Patterns in the future.

Get my free e-book to prepare for the technical interview or start to Learn Full-Stack JavaScript

Discussion (0)