What are Factory Functions and Constructors?

0 Shares
0
0
0
0

Introduction

To understand the concept of factory functions and constructors, you must first understand functions and objects.

If you create a lot of objects with similar properties and functions, it can get boring and repetitive. We want to keep the DRY code principle in mind, which stands for “Don’t Repeat Yourself.” There are functional patterns that can help us write shorter, cleaner, and more efficient code. Factory functions and constructor functions can do the job, but which one should you use? Let’s dive a little deeper into what factory functions and constructors are, what they can and can’t do, and the similarities and differences.

What is Factory Function?

A factory function can be thought of as a real factory that receives raw materials and produces multiple products quickly. Factory functions, on the other hand, receive specific inputs and use those inputs to create a new object. So, how can this actually be useful to us? We could simply create each object individually, but it might take a while. If you are creating objects with the same properties and different values, creating a factory function can speed up the process.

const car1 = {
make: 'Toyota',
model: 'Tacoma',
year: 2018,
fuelType: 'gas', 
bodyType: 'mid-size pick-up truck',
getFullName() {
return `${this.year} ${this.make} ${this.model}`;
}
}
console.log(car1.getFullName()); // => 2018 Toyota Tacoma

In the above example, we create an object that describes a specific machine. Now let's create an object similar to this one.

const car2 = {
make: 'Tesla', 
model: 'Model S',
year: 2018,
fuelType: 'electric',
bodyType: 'sedan',
getFullName() {
return `${this.year} ${this.make} ${this.model}`;
}
}
console.log(car2.getFullName()); // => 2018 Tesla Model S 

Now, I could go on and on about making more machine objects, but who has time for that, right? Let's see how this works as a factory function.

function createCar (make, model, year, fuelType, bodyType) {
return {
make: make, 
model: model, 
year: year, 
fuelType: fuelType, 
bodyType: bodyType,
getFullName() {
return `${year} ${make} ${model}`;
}
}
}

We now have a factory function that creates new objects for us. Now, all we need to do is pass the data and let the factory function do its job.

function createCar (make, model, year, fuelType, bodyType) {
return {
make: make, 
model: model, 
year: year, 
fuelType: fuelType, 
bodyType: bodyType,
getFullName() {
return `${year} ${make} ${model}`;
}
}
}
const car1 = createCar('Toyota', 'Tacoma', 2018, 'gas', 'mid-size pick-up truck');
const car2 = createCar('Tesla', 'Model S', 2018, 'electric', 'sedan');
console.log(car1.getFullName()); // => 2018 Toyota Tacoma
console.log(car2.getFullName()); // => 2018 Tesla Model S

You can create any number of objects using a factory function instead of repeating code.

What are constructors?

The constructor function is another JavaScript pattern that is very similar to factory functions. However, unlike factory functions, constructor functions do not actually return an object. To create different objects with the same properties, we need to use the “new” keyword and the “this” keyword. With that said, let’s see how constructor functions work visually.

function Car(make, model, year, fuelType, bodyType) {
this.make = make
this.model = model 
this.year = year
this.fuelType = fuelType
this.bodyType = bodyType
this.getFullName = () => {
return `${this.year} ${this.make} ${this.model}`;
}
}

In the above example, the constructor function is very similar to a factory function, except for the use of the keyword “this”. ”this” refers to the constructor function that created the instance object. In other words, “this” has no value in a constructor function. You can think of it as a base for the new object.

function Car(make, model, year, fuelType, bodyType) {
this.make = make
this.model = model 
this.year = year
this.fuelType = fuelType
this.bodyType = bodyType
this.getFullName = () => {
return `${this.year} ${this.make} ${this.model}`;
}
}
const car1 = new car('Toyota', 'Tacoma', 2018, 'gas', 'mid-size pick-up truck');
const car2 = new car('Tesla', 'Model S', 2018, 'electric', 'sedan');
console.log(car1.getFullName()); // => 2018 Toyota Tacoma
console.log(car2.getFullName()); // => 2018 Tesla Model S

To create objects using constructor functions, we use another keyword called “new.” When you use the “new” statement in front of a function call, JavaScript automatically does two things for us: Inside the function, it creates an empty object named “this.” It returns the “this” object to the statement that originally called the function.

function Car(make, model, year, fuelType, bodyType) {
// const this = {};
this.make = make
this.model = model 
this.year = year
this.fuelType = fuelType
this.bodyType = bodyType
this.getFullName = () => {
return `${this.year} ${this.make} ${this.model}`;
}
// return this;
}
const car1 = new car('Toyota', 'Tacoma', 2018, 'gas', 'mid-size pick-up truck');
const car2 = new car('Tesla', 'Model S', 2018, 'electric', 'sedan');
console.log(car1.getFullName()); // => 2018 Toyota Tacoma
console.log(car2.getFullName()); // => 2018 Tesla Model S

Inheritance

Inheritance plays an important role in the difference between factory and builder functions.

function createCar (make, model, year, fuelType, bodyType) {
return {
make: make, 
model: model, 
year: year, 
fuelType: fuelType, 
bodyType: bodyType,
getFullName() {
return `${year} ${make} ${model}`;
}
}
}
const car1 = createCar('Toyota', 'Tacoma', 2018, 'gas', 'mid-size pick-up truck');
const car2 = createCar('Tesla', 'Model S', 2018, 'electric', 'sedan'); 
car1.getFullName = function() {
return `My ${fuelType} ${bodyType} is a ${year} ${make} ${model}`;
}
console.log(car1.getFullName()); // => My gas mid-size pick-up truck is a 2018 Toyota Tacoma
console.log(car2.getFullName()); // => 2018 Tesla Model S

Let's go back to the first example of a factory function. What if we want to re-declare car1.getFullName()? Well, car1.getFullName() and car2.getFullName() are not the same function in memory. Each object gets its own copy of the function. This means that when the function creates an object and returns it, it copies the properties and values and binds them to each object that calls the function.

function car(make, model, year, fuelType, bodyType) {
// const this = {};
this.make = make
this.model = model 
this.year = year
this.fuelType = fuelType
this.bodyType = bodyType
this.getFullName = () => {
return `${this.year} ${this.make} ${this.model}`;
}
// return this;
}
const car1 = new car('Toyota', 'Tacoma', 2018, 'gas', 'mid-size pick-up truck');
const car2 = new car('Tesla', 'Model S', 2018, 'electric', 'sedan');
console.log(car1); // => car {make: 'Toyota', model: 'Tacoma' , etc.}

Now let's take a look at our constructor function above. When the constructor is created, it has its own prototype. When we create a new Car object using the "new" keyword, it creates an instance of the Car type. In other words, the prototype of car1 is of the Car type. Now car1 inherits from the Car constructor. This allows us to add properties to the Car prototype.

Car.prototype.sentence = function() {
return `My ${this.fuelType} ${this.bodyType} is a ${this.year} ${this.make} ${this.model}`;
}
console.log(car1); // => Car {
// make: 'Toyota',
// model: 'Tacoma',
// year: 2018,
// fuelType: 'gas',
// bodyType: 'mid-size pick-up truck',
// getFullName: [Function (anonymous)]
// }

Note that the sentence function is not added directly to the car builder itself.

console.log(Car.prototype); // => { sentence: [Function (anonymous)] }

But when we check its prototype, there it is! We can now access the newly added function.

console.log(car1.sentence()); // => My gas mid-size pick-up truck is a 2018 Toyota Tacoma
console.log(car2.sentence()); // => My electric sedan is a 2018 Tesla Model S

Now that we know how each operation works, the question is: which one should we use? Both can give you the same result. Constructors are great when you want to add or remove a property from any object that inherits from the constructor. However, factory functions can be easier to understand because at the end of the day, it is just a function. For factory functions, we don’t need to introduce the “new” keyword. With the power of closures, it can be more flexible. With it, we can do something called “data privacy”. Let’s look at another example that explains how this works.

function createCar(make, model, year) {
return {
getFullName() {
return `${year} ${make} ${model}`;
}
}
}
const car1 = createCar('Toyota', 'Tacoma', 2018); 
console.log(car1.getFullName()); // => 2018 Toyota Tacoma
console.log(car1.make); // => undefined
console.log(car1); // => { getFullName: [Function: getFullName] }

Result

Overall, the important thing about writing your own code is that there is nothing necessarily wrong with it. There is a good way to keep our code clean and short. Use factory functions and constructor functions where you think it is best for your case.

Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like