Object default values and use cases of builder pattern

Object Default values

In this article I will continue covering design patterns. Today's tutorial is about builder strategy pattern. It has can be very beneficial to maintain clean code. There are few tips and tricks which can help you to identify and use builder pattern. 

let  user = {
  name: "Tim", 
  lastName: "Benoi",
  address: {
    street: "Picadilly", 
    post: "212 14"
  }, 
  telefon: [
    {
      homeNumber: "761222122"
    },
    {
      workNumber: "761222122"
    }
  ]
}

We can access object parameters through dot, object destructuring as discussed in the previous post. If you would like to read more about it follow the link. here =>  https://webdevs.se/admin/content/posts/edit/18


let { name, lastName, address: { street, post }, telefon} = user

However, let's imagine the case scenario where we access variables which are not defined on user object

let { name, lastName, address: { street, post }, telefon, companyName, position} = user

as you can see we assigned companyName and position variables with user object. However user object does not have those variables. As a result javascript will set default values of undefined. 

// companyName => undefined, position => undefined

This where default values comes in. 

let { name, lastName, address: { street, post }, telefon, companyName = "Invajo", position = "developer"} = user

//console.log(company) companyName => Invajo, console.log(position) position => developer

// we encapsulate work related information into an object

let { name, lastName, address: { street, post }, telefon, work = { name:"Invajo", position:"developer" }} = user

// => console.log(work) {name: "Invajo", position: "developer"}

Default values can be used in functions and there are lots of other beneficial use case scenarios, however it is not in the scope of this article. Later on we can see how this technique can be useful in construction of builder pattern.  

Business case: Car manufacturer 

Let 's imagine that we are car manufacturer that sells different kind of cars and allows its customers to modify certain parts of the car. Like engine, air conditioning, automatic switcher, autopilot and etc. In order to that we first create a car with basic configuration and then write the logic to enable addition of extra features.

class CarBuilder {
  constructor(engine, fuelType, autopilot, navigation) {
    this.engine = engine
    this.fuelType = fuelType
    this.autopilot = autopilot
    this.navigation = navigation
  }
}

// let define our customer engine

class Engine {
  constructor(model, horsePower) {
    this.model = model
    this.horsePower = horsePower
  }
}

// our default engine
class defautlEngine {
  constructor() {
    this.model = "F100"
    this.horsePower = 120
  }
}

// let's assume our customer made choice of only

let engine = new Engine("F130", 390)
let customerCar = new CarBuilder(engine, "petrol", true, true)

// console.log(customerCar)
// => Car; {engine: {horsePower: 390, model: "F130"}, fuelType: "petrol", autopilot: true, navigation: true}

In this case our customer chooses all four configuration options which we had to offer, now what will happen if our customer would like to buy car with only two configurations. As you can see our first placeholder is engine, that's why we have to  pass undefined for engine, autopilot and navigation.

let customerCar1 = new Car(new Engine("F100", 120), "petrol", false, true)
// engine with model F130 and horse power 120 is our default engine
// console.log(customerCar1) => Car;{engine:{horsePower:120, model: "F100"}, fuelType: "petrol", autopilot: false, navigation: true}

As you can see it would be hard to continue with this current logic, imagine we have 100 configurations and we have to assign correct values based on how variables are placed in our class constructor. This is where builder pattern comes in. We can dynamically build car based in inputs. Let's first define our default car class

class defaultEngine {
  constructor() {
    this.model = "F100"
    this.horsePower = 120
  }
}

// engine builder
class engineBuilder {
  constructor() {
    this.engine = new defaultEngine()
  }
  
  setModel(model) {
    this.engine.model = model
    return this
  }
  
  sethorsePower(hp) {
    this.engine.horsePower = hp
    return this
  }
  
  buildEngine() {
    return this.engine
  }
}

class defaultCar {
  constructor() {
    // we are building simple engine
    this.engine = new engineBuilder()
    this.fuelType = "diesel"
    this.autopilot = false
    this.navigation = false
    
  }
}

class carBuilder {
  constructor() {
    this.car = new defaultCar()
  }
  
  setEngine(engine) {
    this.car.engine = engine
    return this
  }
  
  setPetrol(fuelType) {
    this.car.fuelType = fuelType
    return this
  }
  
  setAutoPilot(autopilot) {
    this.car.autopilot = autopilot
    return this
  }
  
  setNavigation(navigation) {
    this.car.navigation = navigation
    return this
  }
  
  setAirconditoner(airConditioner) {
    this.car.airConditioner = airConditioner
    return this
  }
  
  setAutomaticBreak(autobreak) {
    this.car.autobreak = autobreak
    return this
  }
  
  buildCar() {
    return this.car
  }
  
}

Our setup is ready, let's actually initialize simple car, with simple engine

let defaultEngine = new engineBuilder().buildEngine()

// => defaultEngine {model: "F100", horsePower: 120}
let simpleCa1 = new carBuilder().setEngine(defaultEngine).buildCar()

// => defaultCar {engine: defaultEngine, fuelType: "diesel", autopilot: false, navigation: false}

// let configure our engine and car

let f130Engine = new engineBuilder().setModel("F130").sethorsePower(450).buildEngine()
let f140car = new carBuilder()
                 .setEngine(f130engine)
                 .setAirconditoner(true).
                 .setFuelType("petrol")
                 .setNavigation(true).
                 .setAutoPilot(true).
                 .setAutomaticBreak(true)
                 .buildCar()
// => 
/*
defaultCar {engine: defaultEngine, fuelType: "diesel", autopilot: true, navigation: true, airConditioner: true, …}
  airConditioner: true
  autobreak: true
  autopilot: true
    engine: defaultEngine
      horsePower: 120
      model: "F130"
  fuelType: "petrol"
  navigation: true

*/

 Let's redo the same pattern utilizing default values.

// same things with default values

class Engine {
  constructor({ model="F120", horsePower=120 } = {}) {
    this.model = model
    this.horsePower = horsePower
  }
}

class Car {
  constructor(engine, { fuelType="diesel", autopilot=false, navigation=false } = {}) {
    // we are building simple engine
    this.engine = engine
    this.fuelType = fuelType
    this.autopilot = autopilot
    this.navigation = navigation
  }
}

// let build f13o engine car with diesel and navigation
// accesssing parameters with object destructuring

let f130Engine = new Engine({ model: "F130"})

// console.log(f130Engine) => { model: "F130", horsePower: 120}

let dieselCar = new Car(f130Engine, { navigation: true })

// console.log(dieselCar) => 
/*
  Car {engine: Engine, fuelType: "diesel", autopilot: false, navigation: true}
    autopilot: false
    engine: Engine
      horsePower: 120
      model: "F130"
  fuelType: "diesel"
  navigation: true

*/

It looks straightforward with object destructuring and default values technique. I hope it brings benefit for you.