Skip to main content

Factory Method

Creating objects without specify exact object class: not calling a constructor directly.

Static Factory Method

CoordinateSystem = {
CARTESIAN: 0,
POLAR: 1,
}

class Point {
constructor(x, y) {
this.x = x
this.y = y
}

static get factory() {
return new PointFactory()
}
}

class PointFactory {
static newCartesianPoint(x, y) {
return new Point(x, y)
}

static newPolarPoint(rho, theta) {
return new Point(rho * Math.cos(theta), rho * Math.sin(theta))
}
}

const point = PointFactory.newPolarPoint(5, Math.PI / 2)
const point2 = PointFactory.newCartesianPoint(5, 6)

Dynamic Factory Method

class Vehicle {
constructor({
type = 'vehicle',
state = 'brand new',
color = 'white',
speed = 0,
} = {}) {
this.type = type
this.state = state
this.color = color
this.speed = speed
}

run(...args) {
if (args.length === 0)
console.log(`${this.type} - run with: ${this.speed}km/s`)
else if (toString.apply(args[0]) === '[object Number]')
this.speed = args[0]
}

withColor(...args) {
if (args.length === 0)
console.log(`The color of this ${this.type} product is : ${this.color}`)
else if (toString.apply(args[0]) === '[object String]')
this.color = args[0]
}

reform(funcName, newFunc) {
if (
typeof this[funcName] === 'function'
|| typeof this.prototype[funcName] === 'function'
) {
delete this[funcName]
this.prototype[funcName] = newFunc
}
}

addFeature(funcName, newFunc) {
if (typeof this[funcName] === 'undefined') {
this[funcName] = newFunc
this.prototype[funcName] = newFunc
}
}
}

class Car extends Vehicle {
constructor({
type = 'car',
state = 'brand new',
color = 'silver',
speed = 10,
doors = 4,
} = {}) {
super({ type, state, color, speed })
this.doors = doors
}
}

class Truck extends Vehicle {
constructor({
type = 'truck',
state = 'used',
color = 'blue',
speed = 8,
wheelSize = 'large',
} = {}) {
super({ type, state, color, speed })
this.wheelSize = 'large'
}
}

class VehicleFactory {
constructor() {
this.VehicleClass = Car
}

createVehicle(options) {
switch (options.vehicleType) {
case 'car':
this.VehicleClass = Car
break
case 'truck':
this.VehicleClass = Truck
break
default:
break
}

return new this.VehicleClass(options)
}
}

class CarFactory extends VehicleFactory {
constructor() {
super()
this.VehicleClass = Car
}
}

class TruckFactory extends VehicleFactory {
constructor() {
super()
this.VehicleClass = Truck
}
}

const vehicleFactory = new VehicleFactory()
const car = vehicleFactory.createVehicle({
vehicleType: 'car',
color: 'yellow',
doors: 6,
})
const movingTruck = vehicleFactory.createVehicle({
vehicleType: 'truck',
state: 'like new',
color: 'red',
wheelSize: 'small',
})

const truckFactory = new TruckFactory()
const bigTruck = truckFactory.createVehicle({
state: 'bad.',
color: 'pink',
wheelSize: 'so big',
})

Asynchronous Factory Method

class DataContainer {
#data
#active = false

#init(data) {
this.#active = true
this.#data = data
return this
}

#check() {
if (!this.#active)
throw new TypeError('Not created by factory')
}

getData() {
this.#check()
return `DATA: ${this.#data}`
}

static async create() {
const data = await Promise.resolve('downloaded')
return new this().#init(data)
}
}

DataContainer.create().then(dc =>
assert.equal(dc.getData(), 'DATA: downloaded')
)