Factory Functions in Javascript: The How and Why
Josh Lee
Posted on March 21, 2024
When I started web development about five years ago, I primarily worked with Ruby. And when I was learning Ruby, I picked up object oriented programming (OOP).
A lot of experienced developers have opinions about using OOP versus something like functional programming. That’s not something I’m going to discuss in this article.
Anyway, after moving from primarily working with Ruby to Javascript, I wanted to see how OOP worked in Javascript. Turns out, OOP in Javascript is a bit different than working with Ruby.
One of the most popular ways to work with objects in Javascript is to use factory functions. And that’s what I’m going over today.
Understanding factory functions:
At its core, a factory function is simply a function that creates objects and returns them. It's like a magic wand that conjures up new instances of objects with just a flick of its wrist:
function factory (){
return {...}
}
You might be wondering, "Why bother with factory functions when I can just create objects directly like this?
const object = {...}
Did this code create an object? Yes! Then why on earth would you need factory function?
Here lies the beauty of factory functions—they solve problems that arise when you're dealing with multiple objects or when those objects share common traits but have slight variations.
The Perks of Factory Functions
Picture this: you're tasked with representing your close friends as objects in your code. Each friend has a talk()
function that announces their name. Initially, you might opt for a straightforward approach:
const friendOne = {
name: "John",
talk(){
return (`Hello I am ${this.name}.`)
}
}
const friendTwo = {
name: "jane",
talk(){
return (`Hello I am ${this.name}.`)
}
}
Now, if you call the talk()
function in the two objects you will get the result as follows.
friendOne.talk()
//Output
Hello I am John.
friendTwo.talk()
//Output
Hello I am jane.
This works like a charm... until it doesn't.
Encountering the Snags
The first problem surfaces when you realize that JavaScript objects are mutable. Changing a friend's name directly could lead to unexpected results:
friendOne.name = "Steve";
friendOne.talk();
// Output: Hello, I am Steve.
Now, the code is completely wrong. And that’s where the problem is. The fact that the name property is exposed and available to overwrite raises a lot of bugs when developing huge applications.
Another major issue is code duplication. Defining the talk()
function separately for each friend object is cumbersome and prone to errors. While it may not seem problematic with just two objects, imagine managing a hundred objects.
If you've delved into programming principles, you've likely encountered discussions about avoiding code duplication. This concept is encapsulated in the principle of DRY (Don't Repeat Yourself), which emphasizes the importance of avoiding redundancy in code to prevent maintenance challenges as your codebase expands.
The solution to these woes? Enter factory functions.
The Hero We Need: Factory Functions
Let's visualize the Tesla car factory for a moment. Raw materials like metal sheets, batteries, leather, and tires enter the production line, and as if by magic, a sleek Tesla car emerges as the output. Factory functions are no different.
Let me show you what I mean. Let's revisit our friend-making scenario, but this time, with a factory twist: Here, we have a function that takes a friendName
parameter and creates the friend object.
function friendFactory(friendName) {
return {
friendName: friendName,
talk() {
console.log(`Hello I am ${friendName}.`)
}
}
};
When a function returns a new object, it becomes a factory function. In this case, the friendFactory
is a factory function since it returns a friend object.
This code below creates a function called friendFactory
, which takes one parameter and returns an object. When you call this function with your friend's name as an argument, it returns an object representing a friend. Each friend object has two properties: friendName
, which stores the name you provided, and the talk()
function that prints a greeting message, including the friend's name.
function friendFactory(friendName) {
return {
friendName: friendName,
talk() {
console.log(`Hello I am ${friendName}.`)
}
}
};
let friendOne = friendFactory('Billy');
let friendTwo = friendFactory('Mary');
friendOne.talk()
friendTwo.talk()
//Output
Hello I am Billy
Hello I am Mary
So when we create friendOne
and friendTwo
using friendFactory('Billy')
and friendFactory('Mary')
respectively, we can then ask them to talk()
, and they'll introduce themselves. It's like summoning two friends, Billy and Mary, who are always ready to say hello!
With the friendFactory
function, you can easily make as many friend objects as you want without having to rewrite the same code each time.
Optimizing with Factory Functions
But now there is another problem. When you make a friend object, JavaScript sets aside some memory space to store these objects. And if you are like me, who has many friends, that becomes a problem.
You have probably heard of time complexity and space complexity in programming. Don’t start googling; I will just simplify it for you here. It simply means writing code that executes fast and uses minimal memory for efficient performance.
One way we can solve the issue with memory space is to avoid repeating the talk()
function in every friend object. Checkout this code.
function friendFactory(friendName) {
return {
friendName: friendName,
}
};
We will then move the talk()
method into another object.
let friendDetails = {
talk() {
return (`Hello I am ${this.friendName}.`)
},
};
Before invoking the talk()
method on the friend object, you can assign the method from the friendDetails
object to the friend object in the following manner:
let friendOne = friendFactory('Brian');
let friendTwo = friendFactory('Jane');
friendOne.talk = friendDetails.talk;
friendTwo.talk = friendDetails.talk;
console.log(friendOne.talk());
console.log(friendTwo.talk());
//Output
Hello I am Brian.
Hello I am Jane.
Real-World Application: Enemy Generation
To illustrate the prowess of factory functions further, let's step into the realm of game development. Imagine crafting a dynamic game where various enemies lurk in the shadows. Each enemy has different properties, such as health, attack power, and speed.
Instead of manually creating each enemy object with repetitive code, you can utilize a factory function to streamline the process.
// Define the factory function for creating enemy objects
function enemyFactory(type, health, attackPower, speed) {
return {
type: type,
health: health,
attackPower: attackPower,
speed: speed,
attack() {
console.log(`${this.type} attacks with ${this.attackPower} power!`);
},
move() {
console.log(`${this.type} moves at a speed of ${this.speed}.`);
}
};
}
// Create different types of enemies using the factory function
let goblin = enemyFactory('Goblin', 50, 10, 5);
let skeleton = enemyFactory('Skeleton', 70, 15, 4);
let troll = enemyFactory('Troll', 100, 20, 3);
// Interact with the created enemies
goblin.attack();
skeleton.move();
troll.attack();
//Output
Goblin attacks with 10 power!
Skeleton moves at a speed of 4.
Troll attacks with 20 power!
In this scenario, a factory function efficiently generates enemy objects with specified characteristics. It saves you from repetitive object creation code and allows for easy customization of each enemy type.
If we were to use JavaScript classes instead, we'd have to define a separate class for each enemy type, resulting in more complex code. Factory functions offer a more flexible and concise solution, especially when dealing with object creation in scenarios where objects share similar structures but differ in specific properties.
Conclusion
So, what's next? I encourage you to embrace factory functions in your projects. Experiment with them, explore their capabilities, and witness firsthand the transformative power they possess.
Remember, the journey doesn't end here. Try out the examples provided, delve deeper into factory functions, and let me know if you found this article helpful. Your feedback fuels my passion for sharing knowledge, and I'm eager to continue this journey with you.
Posted on March 21, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.