The Factory Method Pattern and Turning Levels Into Numbers

The four pieces every factory method setup shares, a common shortcut that looks like this pattern but is not, and where it actually pays off at scale.

September 12, 20263 min read12 / 19

The last post built RandomAnimalFactory and BalancedAnimalFactory, both swappable behind the same AnimalFactory shape.

Here's the idea in one sentence: a single hardcoded creation function is a real improvement over copy-pasted logic, but it only becomes this pattern once there's a shared shape more than one factory can implement.

The General Shape

This pattern always has the same four pieces, whatever it's building.

A product
The shared shape of the thing being built. Here, `Animal`.
A concrete product
One real, buildable version of it. Here, `Dog`, `Cat`, `Duck`.
A creator
The shared shape of something that builds a product. Here, `AnimalFactory`.
A concrete creator
One real way of deciding which product to build. Here, `RandomAnimalFactory`, `BalancedAnimalFactory`.

A Single Factory Function Isn't Quite This Pattern

It's tempting to stop one step earlier: just write one function somewhere that randomly builds a dog, cat, or duck, and call that function everywhere you need an animal. That's a real improvement over copy-pasting the random logic everywhere, but it's missing the actual point of this pattern.

A single hardcoded function can't be swapped. There's no AnimalFactory shape for RandomAnimalFactory and BalancedAnimalFactory to both share, so there's nothing to pass around, and nothing calling code could accept instead. The moment you want a second way of deciding what to build, and a way to choose between them at runtime, you need the shared shape, not just one function.

Turning Levels Into Numbers

Here's where this pattern earns its keep at scale. Picture a game that spawns asteroids, and the asteroids need to be bigger and faster on harder levels.

The tempting first move is a class per level: Level1AsteroidFactory, Level2AsteroidFactory, and so on. That's the same class explosion the strategy pattern post ran into with duplicated flying code, just wearing a level number instead.

The fix is the same idea too: instead of a class per level, one AsteroidFactory that takes a size-likelihood and a speed as constructor parameters. Level ten just passes bigger numbers than level one. The level number turns into a property fed into one factory, instead of a whole new class.

Run this yourself: All code examples in this series are in the code-practice repo. Clone it, then run g++ -std=c++17 main.cpp -o main && ./main (C++) or npm start (TypeScript) to see it run.

Try It Yourself

An e-commerce order needs an alert sent the moment it ships: email for a normal order, SMS for an order over a certain value, and a push notification if the customer has the app installed. Which one gets built depends on the order itself, decided the moment the order ships.

  1. Design an Alert product with a send() method, and an AlertFactory creator with createAlert().
  2. Build EmailAlert, SmsAlert, and PushAlert as concrete products.
  3. Build OrderAlertFactory as the concrete creator. Its createAlert() should look at the order's value and the customer's app status to decide which one to build.
  4. Call it from a notifyCustomer(factory, order) function that never mentions EmailAlert, SmsAlert, or PushAlert by name.

The Essentials

  1. A single hardcoded factory function is a real improvement over duplicated logic, but it isn't this pattern until there's a shared shape that more than one factory can implement.
  2. At scale, a factory that takes parameters (like a size-likelihood and a speed) replaces a whole family of near-identical classes, one per level or scenario.