Factory Functions in Javascript: The How and Why

heyjoshlee

Josh Lee

Posted on March 21, 2024

Factory Functions in Javascript: The How and Why

Image description

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 {...}
}
Enter fullscreen mode Exit fullscreen mode

You might be wondering, "Why bother with factory functions when I can just create objects directly like this?

const object = {...}
Enter fullscreen mode Exit fullscreen mode

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}.`)
   }
}
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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}.`)
       }
   }
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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,
    }
};
Enter fullscreen mode Exit fullscreen mode

We will then move the talk() method into another object.

let friendDetails = {
   talk() {
       return (`Hello I am ${this.friendName}.`)
   },
};
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
heyjoshlee
Josh Lee

Posted on March 21, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related