JavaScript Design Patterns Part 1: The Factory Pattern
Lately as the projects I've had the opportunity to work on have grown in scale, I've been taking the time to delve deeper into design patterns for writing more maintainable and scalable Javascript code. Design patterns are a great way to apply time and battle tested solutions to common problems so we can more quickly and efficiently solve them.
Most of the design patterns we'll cover are based on Object Oriented Programming and as such it only makes sense that we begin by taking a look at a creational pattern so called because the pattern provides us with a clear interface to create objects while abstracting away the varied complexity or logic involved in creating them. This pattern, is called the Factory Pattern and it allows us to easily create objects in JavaScript.
Coming from other OOP, class-based languages, one might be tempted to think that what we'll be doing in the lines of code below is creating classes and instances but in reality this is just syntactic sugar made to look like syntax from a class based language.
What we're actually doing is leveraging JavaScript's prototypal inheritance and OLOO - Objects Linking to Other Objects to create objects with a shared prototype. The prototype itself is just a plain JavaScript object and not a class in the true sense of the word. A great explanation of inheritance in Javascript and it's differences from classical inheritance can be found in Eric Elliot's article here.
Let's dive into some code.
All the examples from this series will be available on Github here and include instructions for how to run the code.
In order to run the code in this article, you'll need to have Node installed on your machine. Follow these instructions if you don't already have it. If you're following along with the repo, you'll find instructions for running the code in the readme.
First things first, let's create a folder. We can call it javascript-design-patterns in this folder, we'll create a factory folder.
The Factory Pattern In Action
The factory pattern wraps a constructor for different types of objects and returns instances of the objects via a simple API. It makes it easy to create different objects by exposing a simple API that return the specified object type.
Let's start by creating our constructors. These functions will be responsible for returning new objects of a specific type when invoked.
In the factory folder, let's create a laptop.js file.
const Laptop = function({ ram, hdd, name }) {
this.ram = ram || 0;
this.hdd = hdd || 0;
this.name = name || "";
};
module.exports = Laptop;
In this file, we create a Laptop constructor function. It accepts an object as a parameter with attributes for instantiating the object with various specs we wish to capture - in this case, RAM size, HDD size and a device name.
After that, we export the Laptop constructor function from the module.
Let's create another file called tablet.js
We'll do the same thing but with specs more relevant to a tablet.
const Tablet = function({ ram, hdd, name, network }) {
this.ram = ram || 0;
this.hdd = hdd || 0;
this.network = network || 0;
this.name = name || "";
};
module.exports = Tablet;
Now that we have our constructors, let us create the factory function that will expose the API for creating new instances of these items. Add a new file called gadgetFactory.js
const Laptop = require("./laptop");
const Tablet = require("./tablet");
const gadget = { Laptop, Tablet };
module.exports = {
createGadget(type, attributes) {
const GadgetType = gadget[type];
return new GadgetType(attributes);
}
};
Here, we start by importing the constructors for creating Laptop and Tablet objects. We then create a gadget object using the constructor names as the keys. This makes it possible for us to access the type of constructor we want using gadget[type] - where in this example, type will be either "Laptop" or "Tablet".
Finally, we export an object from this module with a createGadget method. This method accepts a gadget type as the first parameter and calls the specified constructor type while passing in the attributes to it.
You should note that when we call a function with the new keyword in Javascript we get in return an empty object with a this binding set to the one in the executing function. This unique call will also create a prototypal relationship between the constructor function and any new objects we create in this way. We'll see this in detail in the other design patterns we'll cover.
Also worthy of note, is that the capital first letter is just a convention and not a requirement. It does not do anything special and we could just as well have named the functions with camelCase as we usually do with other variable and function names in JavaScript.
At this point, we can now create the file that will make use of (or consume) our factory pattern API.
Create an index.js file and add the following code.
const gadgetFactory = require("./gadgetFactory");
const myLaptop = gadgetFactory.createGadget("Laptop", {
ram: 8,
ssd: 256,
name: "Bab's MacBook Pro"
});
const myTablet = gadgetFactory.createGadget("Tablet", {
ram: 4,
hdd: 128,
name: "Bab's iPad",
network: '4G'
});
console.log(myLaptop);
console.log(myTablet);
First thing you might notice is that in this file, we don't require the constructors for laptops and tablets directly. All we need to require is the gadgetFactory module (with it's createGadget method). Using this method we then create two instances of a laptop and tablet respectively and log them out to the console.
Now in your terminal navigate to the javascript-design-patterns folder and type:
$ node ./factory/index.js
You should see the following logged to the console:
Laptop { ram: 8, ssd: 256, name: 'Bab\'s MacBook Pro' }
Tablet { ram: 4, hdd: 128, network: '4G', name: 'Bab\'s iPad' }
As you can see, we created one Laptop object type as well as a Tablet type, each with their own specifications. Using this pattern you could create as many gadget objects as you require each with their own specifications.
And that's it for the factory pattern. This is a rather simplistic implementation of course, and in anything other than a trivial app you'd definitely want to include more stringent logic - around your constructors, for instance.
In this example, we used Javascript's constructor functions but this pattern can also be implemented using prototypes.