DEV Community

Cover image for JS ES6 Design Patterns: Factory
sanderdebr
sanderdebr

Posted on

JS ES6 Design Patterns: Factory

Design patterns can make your code more flexible, more resilient to change and easier to maintain. In this post you will learn to use the the factory pattern in vanilla ES6 using an object-orientated way of programming.

What is the Factory pattern?
The factory pattern is a creational design pattern, which means it deals with object creation. There are 3 types of factory patterns:

  1. Simple factory
  2. Factory method
  3. Abstract factory.

Let's see what they are!


🔨 Simple factory

Generates an instance without exposing any instantiation logic to the client.

When to use?
To avoid repeating the same code to generate objects, place it into a dedicated factory instead.

Example
In this example we'll create a factory that returns a Monster with private fields:

// Simple factory
class Monster {
  constructor(type, level) {
    this._type = type;
    this._level = level;
  }

  get type() {
    return this._type;
  }

  get level() {
    return this._level;
  }
}

const MonsterFactory = {
  makeMonster: function (type, level) {
    return new Monster(type, level);
  },
};

const dragon = MonsterFactory.makeMonster("Dragon", 17);
console.log(dragon.level);
Enter fullscreen mode Exit fullscreen mode

🏭 Factory method

Provides a way to delegate instantiation logic to child classes.

When to use?
When the client does not know what exact sub-class it might need.

Example
In the following example we create two players: a Warrior and a Knight which both inherit from the Player class. For each player we'll call the fightMonster() method, which is described in the Player class. The actual monster that is created, depends on the implementation of the makeMonster method of the players themselves. The Warrior creates a Dragon monster with a health of 50 and after attacking it drops with 10 points:

class Dragon {
  constructor() {
    this.health = 50;
  }

  attack() {
    this.health -= 10;
  }
}

class Snake {
  constructor() {
    this.health = 40;
  }

  attack() {
    this.health -= 20;
  }
}

class Player {
  fightMonster() {
    const monster = this.makeMonster();
    monster.attack();
    return monster;
  }
}

class Warrior extends Player {
  makeMonster() {
    return new Dragon();
  }
}

class Knight extends Player {
  makeMonster() {
    return new Snake();
  }
}

const player1 = new Warrior();
console.log(player1.fightMonster());

const player2 = new Knight();
player2.fightMonster();
Enter fullscreen mode Exit fullscreen mode

Abstract factory

Encapsulate a group of individual factories with a common goal. It separates the details of implementation of a set of objects from their general usage.

Imagine that you have a furniture shop with chair's and sofa's. Let's say you want to categorize them in e.g. Victorian and Modern furniture. You don't want to change existing classes as future vendors update their catalogs very often.

When to use?
When your code needs to work with various families of related products, but you don’t want it to depend on the concrete classes of those products—they might be unknown beforehand or you simply want to allow for future extensibility.

Example
In the example below we'll set up a class Application that takes in a factory. Based on the type of factory, e.g. a Windows factory, a certain type of Button gets returned. In our case a WinButton as the factory we provide is the WinFactory.

class WinFactory {
  createButton() {
    return new WinButton();
  }
}

class MacFactory {
  createButton() {
    return new MacButton();
  }
}

class WinButton {
  paint() {
    console.log("Rendered a Windows button");
  }
}

class MacButton {
  paint() {
    console.log("Rendered a Mac button");
  }
}

class Application {
  factory;
  button;

  constructor(factory) {
    this.factory = factory;
  }

  createUI() {
    this.button = factory.createButton();
  }

  paint() {
    this.button.paint();
  }
}

let factory;
let OS = "Windows";

if (OS === "Windows") {
  factory = new WinFactory();
} else if (OS == "Mac") {
  factory = new MacFactory();
}

const app = new Application(factory);

app.createUI();
app.paint(); // Output: Rendered a Windows button
Enter fullscreen mode Exit fullscreen mode

And that's it!

More design patterns are coming, thanks for following this tutorial.

Discussion (1)

Collapse
cyc profile image
Daniel • Edited on

Why Players makes monsters, doesn't make sense to me... Player should fight monsters not make them.