👩‍💻 chrismanbrown.gitlab.io

Functional Factories in JavaScript

An argument against using classes in JavaScript, and using factories instead.

2019-12-15

Contents

  1. What: Introductions and Definitions
    • Background
    • Factories
  2. Why: Problems with Classes
    • There Are No Classes in JavaScript
    • Promotion of “Bad JavaScript”
    • Classical Inheritance
      • The Sandwich Problem
      • The Diamond Problem
      • The Banana Problem
    • Wrong Abstraction
  3. How: Using Factories
    • Factory 101: Creating multiple instances
    • Mixins: Extending a base class
  4. Conclusion
  5. More: Resources and Notes
    • Further Reading
    • Glossary

What: Introductions and Definitions

Background

This article started out as a response to The Factory Pattern - Design Patterns meet the Frontend by Colum Ferry on dev.to, but at this point I think I rambled in enough different directions that it is no longer a direct response. But that’s where my head was at when I started writing this. And now you know.

Factories

The Factory Pattern is a classic design pattern from the world of OOP, where it is used for one or both of the following reasons:

  1. to create multiple instances of a class, and/or

  2. to extend a base class. e.g. a VehicleFactory might return a Car or a Plane while hiding the implementation details for a Vehicle.

See the previously mentioned dev.to article for an overview of that functionality.

My intent here is to show how this pattern, with slight modifications, can be used in Functional Programing for pretty much the same reasons.

Why: Problems with Classes

I advocate for using objects and factories and mixins because they are good abstractions, patterns, and practices.

I also advocate for not using classes because I think they are not good abstractions.

This section (not so) briefly touches on my main reasons for disliking JS classes, and as such, is intended to serve as a sort of motivation for not using them, and for using the recommended alternatives instead.

The reasons I don’t like JS classes are these:

  1. There Are No Classes in JavaScript
  2. Promotion of “Bad JavaScript”
  3. Classical Inheritance
  4. Wrong Abstraction

There Are No Classes in JavaScript

Look, words matter and telling the truth is important.

These are the facts:

JavaScript classes, introduced in ECMAScript 2015, are primarily syntactical sugar over JavaScript’s existing prototype-based inheritance. The class syntax does not introduce a new object-oriented inheritance model to JavaScript.

mdn

Before talking about anything else whatsoever–before talking about things like effeciency, correctness, or expressiveness–classes are dishonest representations of how JavaScript works. They hide and obfuscate the way prototypes behave.

At the very worst, they are a gotcha for programmers coming from OOP languages like Java that actually have classes, and given that context will expect JS classes to behave a certain way. (In fairness, JS classes mostly do behave like classes. But they’re still not classes.)

Promotion of “Bad JavaScript”

Before the widespread adoption of classes, it was my personal observation that it was easier to avoid both seeing and using problematic language keywords like new and this. I see them more frequently now as people wrangle with scoping and binding because of classes.

It also introduces new problematic complexity through constructors and super. It only takes a few seconds to remember whether you have to call super or not, but that lost time is a distraction.

Classical Inheritance

One of the reasons that OOP is broken/falling out of favor/heavily criticized is because of classical inheritance. And people know this. Even within the OOP community, it is known that composition is favored over inheritance.

JS classes promote classical inheritance through use of the extends keyword. That is, creating “is a” relationships between objects: a car “is a” vehicle, and a Toyota “is a” car, and a Prius “is a” Toyota.

Classical inheritance, as a means of describing data, starts to break down fairly quickly given a small degree of complexity for a couple reasons, including the following:

  1. The Sandwich Problem
  2. The Diamond Problem
  3. The Banana Problem

The Sandwich Problem

There’s a whole contentious meme about the Sandwich Problem. People seriously get up in arms about it. My cousin won’t even speak to me any more because of the fight we had last Thanksgiving about sandwiches.

Here’s your chance to play along, Dear Reader. How many of the following items have an “is a” relationship to “Sandwich?”

Look, here’s the deal. The most basic definition of a sandwich is this: some kind of a filling (usually meat, cheese, and/or veggies), enclosed by some kind of a container/wrapper (usually bread).

The very second you try to enforce more than that, stuff falls apart:

When your job is to describe the relationships between data, when your job is literally to sit around and think about stuff like “is x a sandwich?” then “is a” quickly starts to fall apart.

Yes, data is relational. But inheritance still sucks. It’s tightly coupled and brittle, and “sandwich” is a nonsense word.

Here’s another approach, to return to our Vehicle example. Sure, a boat and a car and a plane are all vehicles. But so is a motorcycle and a bicycle and a horse. A vehicle can have some number of wheels, or some number of hooves.

What defines a vehicle? Some means of moving you from Point A to Point B? In that case, is a human itself a vehicle?

Literally, what is a vehicle? What is the common ancestor of “horse”, “airplane,” and “rollerblade?”

Given this mental model, for most things you’re trying describe, you continue to abstract away the common attributes and behaviors for each thing until the base class is something totally stupid and useless like “object.”

The Diamond Problem

The problem of inheriting from multiple parent classes. Also called, appropriately, the Multiple Inheritance problem.

“Diamond” refers to the shape of the inheritance graph:

   [A]      A: office machine
  /   \     B: printer
[B]   [C]   C: copier
  \   /     D: printer/copier
   [D]

If you are creating a multifunction office machine like a printer/copier/fax/espresso-maker, then when you’re creating functionality for Object D, you may not need to implement your own version of print because printers (Object B) and copiers (Object C) both print. But how does the program know which parent print to use?

Most languages take care of this decision for you as a feature of that language, so you don’t have to think about it that much. But the point is, the problem is there. It’s a flaw inherint to this style of thinking.

The Banana Problem

Lastly, the Banana Problem famously describes the problem with state and environment, and a long chain of inheritance.

…the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.

Joe Armstrong, Coders at Work

Wrong Abstraction

Classes are also the wrong abstraction some/most of the time. Or least they’re a limited abstraction.

Sometimes you do want some kind of a grouping of data and functions. It’s tempting to just reach for a class in that case, but you can do the exact same thing with a plain old object, or maybe a function that closes over some data and returns an object.

The point is that you can and should be a little more discerning about the representation you need, and use an abstraction that is only as complicated as you need it to be. First choose an object. Then a function. Then maybe a factory function. And then maybe a class. Instead of just always using class, class, class, and class.

It’s kind of a case of “If the only tool you have is a hammer, every problem looks like a nail.” The ubiquity of JS classes might lead you to believe that classes are the only tools you have, but they are not.

How: Using Factories

Anyway, enough about stuff that’s bad. How about some good stuff that you should use? Back to Functional Factories in JS!

Object factories create multiple objects, and can “extend” a “base class” through functional mixins.

Factory 101: Creating multiple instances

This part is easy. Imagine a car.

Here is an example of object destructuring with default values:

const createCar = ({
  make = 'toyota',
  model = 'prius',
  mpg = 80,
  seats = 4
} = {}) => {
  make, model, mpg, seats
}

That’s an object factory. A function that recieves some data and returns an object.

You can now easily do the following:

const car = createCar({
  make: 'Ford',
  model: 'F350',
  mpg: 10,
  seats: 2,
})

Boom đź’Ą

No new, no this. Just plain old objects. Create as many cars as you want.

Mixins: Extending a base class

Okay, this is where things get a whole lot more functional.

Functional mixins are a good way to avoid and overcome many of the challenges and downfalls of classical inheritance.

They are able to do this because they, as one is encouraged to do even in OOP communities, favor composition over inheritance.

Imagine, again, we are describing vehicles.

Here’s a mixin for movement:

const withMovement = o => {
  let isMoving = false

  return Object.assign({}, o, {
    move() {
      isMoving = true
      return this
    },
    stop() {
      isMoving = false
      return this
    },
    isMoving: () => isMoving,
  })
}

I can pass it an empty object and get back an object that has a couple functions on it related to movement. Neat. No muss, no fuss.

Here’s one for capacity:

const withCapacity = (capacity = 2) => o => {
  let isFull = false;
  let availableSeats = capacity;

  return Object.assign({}, o,
    {
      getAvailableSeats: () => availableSeats,
      getCapacity: () => capacity,
      isFull: () => isFull,
      fillSeats(n) {
        if (n <= availableSeats) availableSeats -= n
        isFull = (availableSeats === 0)
        return this
      },
    }
  )
}

It takes a “capacity” parameter (with a default value of 2) and then an object o, and returns a new object that combines o with some capacity related functions.

Maybe a really simple mixin that creates a new object with some number of wheels:

const withWheels = (wheels = 4) => o => Object.assign({}, o, {
  wheels,
})

So now I have a few mixins to play with. I can mix and match them to create a factory for a certain kind of vehicle. Say, a car:

const carFactory = ({ capacity, wheels }) => pipe(
  withWheels(wheels),
  withMovement,
  withCapacity(capacity),
)({})

This factory takes an “init” type object including capacity and wheels, and then passes those init values into a pipe of mixins.

Aside: If you’re not familiar with pipe or compose, just know for now that it’s a way to pass a value through a chain of functions, getting a single output at the end. In this case, it’s the o object in the mixins above. Here’s the definition: const pipe = (...fs) => (x) => fs.reduce((y, f) => f(y), x)

Anyway, now that we have a car factory, we can create some cars:

const bigRig = carFactory({
  capacity: 2,
  wheels: 18,
})

const bus = carFactory({
  capacity: 100,
  wheels: 4,
})

Look at that! We have achieved our two goals:

  1. We can create multiple, unique objects with our factory

  2. They have shared functionality, but not through class or even function inheritance, but through the assembly of a few functional mixins

If you want to create some different vehicles, you just keep playing Legos and add/remove some additional mixins:

const planeFactory = ({ capacity }) => pipe(
  // withWheels(wheels),
  canFly,
  withMovement,
  withCapacity(capacity),
)({})
const bikeFactory = ({ capacity = 1, wheels = 2 }) => pipe(
  nonMotorized,
  withWheels(wheels),
  withMovement,
  withCapacity(capacity),
)({})
const horseFactory = ({ capacity = 1, legs = 4 }) => pipe(
  nonMotorized,
  isAlive(legs),
  withMovement,
  withCapacity(capacity),
)({})

So you’re no longer creating brittle, tightly coupled relationships between parent and child classes, or even worse, child and parent and grandparent and great-grandparent. Instead you have a series of entities that just happen to share various “can”, “with/has”, and “is” qualities and attributes.

One place you see this kind of composition is the Entity Component System pattern in video game development. This post was one of my first insights into how powerful a concept it is.

Conclusion

I don’t like classes in JavaScript, but I do like plain old objects, object factories, and functional mixins. I think they allow a more natural way of describing data, and that they allow for data to grow in complexity without being too fragile or tightly coupled.

More: Resources and Notes

Further Reading

Glossary

class
In “the map is not the terrain” fashion, a class is not an object, but is a description of an object that can be used to create an object.
…except for in JavaScript, where a class is just syntactic sugar for a plain old JavaScript object, providing some convenient access to its prototype.
FP
Functional Programming: a style of programming that avoids side effects and embraces snobbery and elitism. Advanced stages of the disease cause the afflicted to say stuff like “A monad is just a monoid in the category of endofunctors, what’s the problem?” and, ultimately, to Lisp.
Factory
That thing which facilitates the churning out of products
Mixin
When you go to Cold Stone and you have a small number of base ice creams, and you can add extra flavor through the incorporation of different mixins like oreos or peanuts. You can do the same thing with functions instead of ice cream and add-ons.
OOP
Object Oriented Programing: a maligned practice of programming that requires the memorization of a dozen or more Design Patterns to be consistantly useful.
Sandwich
No idea. Literally a nonsense word that has no meaning and no definition.