The factory pattern in JavaScript
In a similar fashion to the discussion about the JavaScript “prototype” versus the prototype creational design pattern, “factory” refers to related but different concepts when it comes to general program design discussions and design patterns.
A “factory,” in the general programming sense, is an object that’s built with the goal of creating other objects. This is hinted at by the name that refers to a facility that processes items from one shape into another (or from one type of item to another). This factory denomination means that the output of a function or method is a new object. In JavaScript, this means that something as simple as a function that returns an object literal is a factory function:
const simpleFactoryFunction = () => ({}); // returns an object, therefore it's a factory.
This definition of a factory is useful, but this section of the chapter is about the factory design pattern, which does fit into this overall “factory” definition.
The factory or factory method design pattern solves a class inheritance problem. A base or superclass is extended (the extended class is a subclass). The base class’s role is to provide orchestration for the methods implemented in the subclasses, as we want the subclasses to control which other objects to populate an instance with.
Implementation
A factory example is as follows. We have a Building
base class that implements a generateBuilding()
method. For now, it’s going to create a top floor using the makeTopFloor
instance method. In the base class (Building
), makeTopFloor
is implemented, mainly because JavaScript doesn’t provide a way to define abstract methods. The makeTopFloor
implementation throws an error because subclasses should override it; makeTopFloor
is the “factory method” in this case. It’s how the base class defers the instantiation of objects to the subclasses:
class Building { generateBuilding() { this.topFloor = this.makeTopFloor(); } makeTopFloor() { throw new Error('not implemented, left for subclasses to implement'); } }
If we wanted to implement a single-story house, we would extend Building
and override makeTopFloor
; in this instance, topFloor
will have level: 1
.
class House extends Building { makeTopFloor() { return { level: 1, }; } }
When we instantiate House
, which is a subclass of Building
, we have access to the generateBuilding
method; when called, it sets topFloor
correctly (to { level:
1 }
).
const house = new House(); house.generateBuilding(); console.assert(house.topFloor.level === 1, 'topFloor works in House');
Now, if we want to create a different type of building that has a very different top floor, we can still extend Building
; we simply override makeTopFloor
to return a different floor. In the case of a skyscraper, we want the top floor to be very high, so we’ll do the following:
class SkyScraper extends Building { makeTopFloor() { return { level: 125, }; } }
Having defined our SkyScraper
, which is a subclass of Building
, we can instantiate it and call generateBuilding
. As in the preceding House
case, the generateBuilding
method will use SkyScraper
’s makeTopFloor
method to populate the topFloor
instance property:
const skyScraper = new SkyScraper(); skyScraper.generateBuilding(); console.assert(skyScraper.topFloor.level > 100, 'topFloor works in SkyScraper');
The “factory method” in this case is makeTopFloor
. The makeTopFloor
method is “not implemented” in the base class, in the sense that it’s implemented in a manner that forces subclasses that wish to use generateBuilding
to define a makeTopFloor
override.
Note that makeTopFloor
in our examples returned object literals, as mentioned earlier in the chapter; this is a feature of JavaScript not available in all object-oriented languages (JavaScript is multi-paradigm). We’ll see different ways to implement the factory pattern later in this section.
Use cases
The benefit of using a factory method is that we can create a wide variety of subclasses without modifying the base class. This is the “open/closed principle” at play – the Building
class in our example is “open” to extension (i.e., can be subclassed to infinity for different types of buildings) but “closed” to modification (i.e., we don’t need to make changes in Building
for every subclass, only when we want to add new behaviors).
Improvements with modern JavaScript
The key improvement we can make with JavaScript is enabled by its first-class support for functions and the ability to define objects using literals (instead of classes being instantiated).
JavaScript having “first-class functions” means functions are like any other type – they can be passed as parameters, set as variable values, and returned from other functions.
A more idiomatic implementation of this pattern would probably involve a generateBuilding
standalone function instead of a Building
class. generateBuilding
would take makeTopFloor
either as a parameter or take an object parameter with a makeTopFloor
key. The output of generateBuilding
would be an object created using an object literal, which takes the output of makeTopFloor()
and sets it as the value to a topFloor
key:
function generateBuilding({ makeTopFloor }) { return { topFloor: makeTopFloor(), }; }
In order to create our house and skyscraper, we would call generateBuilding
with the relevant makeTopFloor
functions. In the case of the house, we want a top floor that is on level 1; in the case of the skyscraper, we want a top floor on level 125.
const house = generateBuilding({ makeTopFloor() { return { level: 1, }; }, }); console.assert(house.topFloor.level === 1, 'topFloor works in house'); const skyScraper = generateBuilding({ makeTopFloor() { return { level: 125, }; }, }); console.assert(skyScraper.topFloor.level > 100, 'topFloor works in skyScraper');
One reason why using functions directly works better in JavaScript is that we didn’t have to implement a “throw an error to remind consumers to override me” makeFloor
method that we had with the Building
class.
In languages other than JavaScript that have support for abstract methods, this pattern is more useful and natural to implement than in JavaScript, where we have first-class functions.
You also have to bear in mind that the original versions of JavaScript/ECMAScript didn’t include a class
construct.
In the final section of the chapter, we learned what the factory method pattern is and how it contrasts with the factory programming concept. We then implemented a class-based factory pattern scenario as well as a more idiomatic JavaScript version. Interspersed through this section, we covered the use cases, benefits, and drawbacks of the factory method pattern in JavaScript.