JS ES6 Design Patterns: Factory
Tags: javascript
Published: 2021-03-10 18:18:03

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!

<hr/>

## 🔨 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:

```javascript
// 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);
``` 

<hr/>

## 🏭 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:

```javascript
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();
```

<hr/>

## 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`.

```javascript
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
```

And that's it! 

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