This page looks best with JavaScript enabled

Node.JS: Composable factory functions over classes

 ·  ☕ 5 min read  ·  ✍️ Iskander Samatov

composable factory functions



Object Oriented paradigms became more popular in Javascript with the introduction of the new ES6 syntax, more specifically class . Classes are great and they definitely help you structure your code better. However, the trouble comes when you want to combine the functionality of multiple classes together and still enforce the modularity in your code. So in this post I want to talk about the Composable Factory Functions(CFF) that let you construct objects that combine multiple reusable features without creating a tightly coupled class hierarchy.

Factory

Factory is a well-known pattern in the programming world. With factory you abstract the creation of the object with a special factory function and only use that function to create the new instances of the object. The consumer of the factory is unaware how the object is created, thus giving us the freedom of the implementation of the factory function. Let’s look at the example.

Generating security tokens

In this sample we’re going to write a simple factory function for generating a security code for communicating between our server and the front-end:

1
2
3
4
5
6
7
jwt = require('jsonwebtoken');

module.exports = function createToken(userEmail) {
    const token = jwt.sign({
        access: userEmail
      }, 'secret');
}

Now, you might be tempted to just use jsonwebtoken library directly in your client code without creating a separate factory method for it. It’s fine if you’re only generating token in one place, but if you have multiple places in your code that generate the token, changing the implementation will be problematic.

Adding admin access

Now let’s imagine in the future you decide to create a special type of admin token based on user’s email. Since we used our factory method in every part of our code to generate tokens, adding this new functionality is easy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
jwt = require('jsonwebtoken');

const ADMINS = ['iskander@example.com'];

module.exports = function createToken(userEmail) {
    let access = userEmail;
    ADMINS.forEach(email => {
        if (email === userEmail)
            access = 'admin'
    });

    const token = jwt.sign({
        access,
      }, 'secret');

    return token;
}

In the sample above we check if user’s email matches any of the admin emails stored in our array property. If it does, we grant our token an admin level access. Again, If we didn’t implement this factory function, adding this new functionality would prove to be difficult.

But this is old news right? You’ve probably came across the factory pattern before and (hopefully) use it yourself in your code on a daily basis. However, in order to explain Composable Factory Functions I had to go over the factory function pattern first.

Composable factory functions

Composable Factory Functions(CFF) is a more advanced Node.JS pattern that builds on top of the factory pattern. In this pattern, factory functions can be composed together to create another factory function with the combined functionality.

Superhero generator

Here’s a simple example of leveraging CFF to construct objects that combine multiple features. Since the new “Avengers” movie is coming out soon, let’s pretend we’re building a superhero creation emulator. We want to create a new type of superheroes based on the basic attributes and abilities we provide.

For this example we’re going to use stampit library for building CFFs. This lightweight module provides us with a simple API for building factory functions.

So here are the 5 basic CFFs:

  • Character – base character that has life points, stamina points and a name
  • Flying – provides the character with an ability to fly
  • Super strength – character with a super-human strength
  • Intelligence – character has a genius level intellect
  • Martial Arts – makes the character a martial arts expert

Let’s start with the basic Character function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const stampit = require('stampit');

const Character = stampit({
  props: {
    name: null,
    health: 100,
    stamina: 100,
  },
  init({ name = this.name }) {
    this.name = name
  }
});
  • props is used to define the default properties of the object
  • init defines the properties that we can assign at the initialization process

Now let’s add the rest of the CFFs!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const Flying = stampit(Character, {
    methods: {
        fly() {
            console.log(`${this.name} takes off in the sky!`)
            stamina --;
        }
    }
  });


  const SuperStrength = stampit(Character, {
    methods: {
        init({ stamina = this.stamina }) {
            this.stamina = stamina
          },
        throwBoulder() {
            console.log(`${this.name} throws a huge rock at the enemy!`)
            stamina -= 2;
        }
    }
  });


  const Intelligence = stampit(Character, {
    methods: {
        useGadget() {
            console.log(`${this.name} shoots a DIY lazer gun!`)
        }
     }
   });


  const MartialArts = stampit(Character, {
    MartialArts: {
        throwBoulder() {
            console.log(`${this.name} does a 'Flying Dragon' kick!`)
            stamina -= 3;
        }
    }
  });
  });

As you can see we have 4 different super-power CFFs, each with it’s own methods. Now let’s see how we can combine them to create composed factory functions:

1
2
3
const IronMan = stampit(Flying, Intelligence);
const Hulk = stampit(SuperStrength, Intelligence);
const Thor = stampit(SuperStrength, Flying);

In the example above we created 3 different hybrid super hero types using our CFFS. Notice how easy it is to combine different factory functions and how loosely coupled our code is. Now imagine if we tried to replicate this example with the class hierarchy. It’s easy to guess that the solution will have more complexity to it.Plus, what if we decided to add more superhero abilities later on? It would not scale very well.

Finally, let’s create some instances of the composed objects:

1
2
3
4
5
const tonyStark = IronMan({ name: 'Tony Stark' });
tonyStark.fly();

const bruceBanner = Hulk({ name: 'Bruce Banner', stamina: 10000 });
bruceBanner.throwBoulder();

Simple isn’t it? When creating the new instances, we must provide the initial values that were defined in the init method for each factory function.

And that’s it for this post! Hopefully you found it helpful. Composable factory functions are a great alternative to classes in a lot of cases. They allow you to build a reusable and loosely coupled code while building complex object functionality.

Share on

Software Development Tutorials
WRITTEN BY
Iskander Samatov
The best up-to-date tutorials on web and mobile development.