November 05, 2021

What is a Factory Function?

edit

Building upon closures, I want to teach you about “factory functions”, too. Factory functions often use closures, so it makes sense to learn them together. So if you don’t know what a closure is or need a refresher, I encourage you to read that blog post first.

A factory function is a function that returns a new object. The key feature of a factory is that its only job is to pump out those items, just like an actual factory.

The simplest factory function returns a static object of keys and values. Ever needed to turn a small list of values into an object? Take that tuple and give it to a factory function, like so:

const createPerson = (name, twitterHandle) => ({
  name,
  twitterHandle: `@${twitterHandle}`,
})

const kyle = createPerson('Kyle', 'kyleshevlin')
console.log(kyle.twitterHandle) // '@kyleshevlin'

That’s not all that interesting, but it can be a useful way to generate objects. If you had a long list of person-related tuples like this, you could use createPerson with Array.map to turn them into props for a React component.

const people = [
  ['Kyle', 'kyleshevlin'],
  ['Jane', 'janeDoe12345'],
  ['John', 'johnDoe67890'],
]

function PeopleList() {
  return (
    <ul>
      {people.map(personTuple => {
        const person = createPerson(...personTuple)

        return (
          <li key={person.name}>
            <Person {...person} />
          </li>
        )
      })}
    </ul>
  )
}

This is a great use of a factory function, but let’s move on to a more interesting use case.

Replacing Classes with Factories

The place I use factories most often is when I want to return an object with methods but use closures to create private methods and values. Even better, I never have to even think about the this keyword.

When I created my data structures and algorithms course on egghead, I used factory functions to create the data structures. Let’s make a createQueue factory to show you how this strategy works.

function createQueue() {
  // Create an array in closure. This is a private variable.
  const queue = []

  // This could have been a private method, if we didn't also want to expose it
  // Notice how simple this is to understand. No need for `this`
  const isEmpty = () => queue.length === 0

  return {
    enqueue(x) {
      queue.push(x)
    },
    dequeue() {
      return queue.shift()
    },
    peek() {
      if (isEmpty()) return undefined
      return queue[queue.length - 1]
    },
    get length() {
      return queue.length
    },
    isEmpty,
  }
}

const queue1 = createQueue()

What’s great about this pattern is that it’s compatible in every browser. Private fields and private methods for JavaScript classes are widely implemented, but not every where (and never will be).

I recently used this pattern to upgrade my shevyjs package. I was able to have truly private values and functions that I couldn’t have back when I originally wrote the code. I highly recommend taking a look at the source code to see a more complex factory function in action.

Composition with Factories

There are 100s of posts out there on the difficulty of building object hierarchies with inheritance. I kind of want to skip that part and jump right to composition.

Let’s imagine we’re building a game with animal characters. Animals are notoriously difficult to create inheritance hierarchies for because of their diversity and literal exceptionality.

In my game, let’s say I want to create Hawk, Penguin and FlyingFish factories. How can I use composition to make this possible?

There are some shared traits with these animals. Hawks and Penguins both have wings, but not FlyingFish. Penguins and FlyingFish both swim, but Hawks don’t. While also Hawks and FlyingFish fly, while Penguins don’t, despite their wings.

Simply put, inheritance can’t help us here. Let’s compose our factories:

First, we can make factories that createBirds and createFish, they will create very small objects.

const createBird = () => ({
  hasWings: true,
})

const createFish = () => ({
  hasFins: true,
})

There are more properties we could add, but we should be careful with adding too many to the base object. Next, we can create factories for objects based on what they do. Focusing on what something does, versus what it is is a great way to find these kinds of compositional pieces:

const createFlyer = () => ({
  canFly: true,
  fly(vx, vy, vz) {
    // Do something with the velocities
  },
})

const createSwimmer = () => ({
  canSwim: true,
  swim(vx, vy, vz) {
    // Do something with the velocities
  },
})

Now that we have those, we can create compositions to create our various animal factories easily:

const createHawk = () => ({
  ...createBird(),
  ...createFlyer(),
})

const createPenguin = () => ({
  ...createBird(),
  ...createSwimmer(),
})

const createFlyingFish = () => ({
  ...createFish(),
  ...createFlyer(),
  ...createSwimmer(),
})

Look how clean that is! Ahh, just fills my heart with joy. Imagine trying to do that with classes?

Summary

Factory functions are functions that return objects. We can use factory functions and closures to have private variables and private functions, only exposing what we want to our consumer through the object we return, all while avoiding the this keyword. Factories make for easy composition of values and functionality. Use them when inheritance can’t solve your problems.


Liked the post?
Give the author a dopamine boost with a few "beard strokes". Click the beard up to 50 times to show your appreciation.
Kyle Shevlin's face, which is mostly a beard with eyes

Kyle Shevlin is the founder & lead software engineer of Agathist, a software development firm with a mission to build good software with good people.

Logo for Introduction to State Machines and XState
Introduction to State Machines and XState
Check out my courses!
If you enjoy my posts, you might enjoy my courses, too. Click the button to view the course or go to Courses for more information.
Sign up for my newsletter
Let's chat some more about TypeScript, React, and frontend web development. Unsubscribe at any time.