What is the JavaScript Factory Pattern?
The Factory Pattern is a design pattern that allows developers to create objects without having to specify their exact class. In other words, it provides an interface for creating objects in a super class, but leaves the actual creation of those objects to the sub-classes. This pattern is useful when you need to create multiple objects that share a common interface, but have different implementations.
How does the Factory pattern work in JavaScript
In JavaScript, the Factory pattern involves creating a function that returns a new object. This function is called the Factory function, and it encapsulates the object creation process. The Factory function can accept parameters that are used to determine the type and configuration of the objects being created. The objects can be created using a constructor function, or they can be created using plain object literals.
Different types of Factory patterns in JavaScript
There are several different types of Factory patterns in JavaScript, including:
- Simple Factory: A factory function that creates objects without using a constructor function.
- Factory Method: A factory method that is implemented in a superclass and allows subclasses to override the creation of objects.
- Abstract Factory: A factory that creates families of related objects without specifying their concrete classes.
- Singleton Factory: A factory that creates only a single instance of an object and returns it on subsequent calls to the factory function.
Implementing Factory Patterns in JavaScript
In JavaScript, the Factory Pattern can be implemented using different techniques. To showcase and factory patterns, we will develop and explain two detailed examples.
For the first example, we will use a function to create different types of objects based on a parameter that is passed to it.
function createObject(type) {
let obj;
if (type === "go") {
obj = {
prop1: "value1",
prop2: "value2",
goMethOne: function () {
console.log("This is go method 1");
},
goMethTwo: function () {
console.log("This is go method 2");
},
};
} else if (type === "linux") {
obj = {
prop1: "value1",
prop2: "value2",
linuxMethOne: function () {
console.log("This is linux method 1");
},
linuxMethTwo: function () {
console.log("This is linux method 3");
},
};
}
return obj;
}
let objA = createObject("go");
let objB = createObject("linux");
console.log(objA);
Output
{
prop1: 'value1',
prop2: 'value2',
goMethOne: [Function: goMethOne],
goMethTwo: [Function: goMethTwo]
}
In this example, we define a function called createObject
that takes a parameter called type
. Based on the value of type
, the function creates an object that has a common interface, but different implementations. The function returns the created object, which can be used by the client code.
We can work with another example to show the factory pattern. In this example, we will use a constructor function to create different types of objects.
function Go() {
this.prop1 = "value1";
this.prop2 = "value2";
}
Go.prototype.method1 = function () {
console.log("This is Go method 1");
};
Go.prototype.method2 = function () {
console.log("This is Go method 2");
};
function Linux() {
this.prop1 = "value1";
this.prop2 = "value2";
}
Linux.prototype.method1 = function () {
console.log("This is Linux method 1");
};
Linux.prototype.method2 = function () {
console.log("This is Linux method 2");
};
function createObject(type) {
let obj;
if (type === "Go") {
obj = new Go();
} else if (type === "Linux") {
obj = new Linux();
}
return obj;
}
let go = createObject("Go");
let linux = createObject("Linux");
console.log(linux);
Output
Linux { prop1: 'value1', prop2: 'value2' }
In this example, we define two constructor functions (Go
and Linux
) that have a common interface, but different implementations. We then define a function called createObject
that takes a parameter called type
and returns an object of the appropriate type. The client code can then use the created objects to call their methods.
Can the Factory pattern be used with JavaScript classes
Yes, the Factory pattern can be used with JavaScript classes. You can create a Factory class that has methods for creating objects of different types. These methods can use the new
keyword to create objects with the specified constructor functions. Here's an example:
class CarFactory {
createSUV() {
return new SUV("Toyota", "RAV4", "2021", "white", ["4WD", "backup camera", "Bluetooth"]);
}
createSedan() {
return new Sedan("Honda", "Civic", "2022", "blue", ["Apple CarPlay", "blind spot monitoring", "sunroof"]);
}
}
var factory = new CarFactory();
var mySUV = factory.createSUV();
console.log(mySUV);
In this example, the CarFactory
class has two methods for creating SUV and sedan objects. These methods use the new
keyword to create new objects of the SUV
and Sedan
classes, which are assumed to exist.
Can the Factory pattern be used with asynchronous code
Yes, the Factory pattern can be used with asynchronous code. You can create an asynchronous Factory function that returns a Promise that resolves to the new object. The object creation logic can use asynchronous functions or Promises to fetch data or perform other tasks before creating the object. Here's an example:
function createCarAsync(type) {
return new Promise(function(resolve, reject) {
// Use setTimeout to simulate an asynchronous delay of 1 second
setTimeout(function() {
var car;
// Create a new car object based on the type argument
if (type === "SUV") {
car = {
make: "Toyota",
model: "RAV4",
year: "2021",
color: "white",
features: ["4WD", "backup camera", "Bluetooth"]
};
} else if (type === "sedan") {
car = {
make: "Honda",
model: "Civic",
year: "2022",
color: "blue",
features: ["Apple CarPlay", "blind spot monitoring", "sunroof"]
};
}
// Resolve the Promise with the new car object, or reject it with an error
if (car) {
resolve(car);
} else {
reject(new Error("Invalid car type"));
}
}, 1000);
});
}
// Call the createCarAsync function with the "SUV" type argument
createCarAsync("SUV").then(function(car) {
console.log(car);
}).catch(function(error) {
console.error(error);
});
The createCarAsync
function is defined as a function that takes a type
argument, and returns a Promise. The Promise constructor takes two arguments: a resolve
function and a reject
function. These functions are called when the Promise is either fulfilled (resolved) or rejected. In this case, we use the setTimeout
function to simulate an asynchronous delay of 1 second, and then create a new car object based on the type
argument. If the type
is not "SUV" or "sedan", we reject the Promise with an error message.
If the type
is "SUV" or "sedan", we create a new car object with some properties, and then call the resolve
function with the new object. This fulfills the Promise with the new object as its value.
Finally, we call the createCarAsync
function with the "SUV" type argument, and use the then
method to handle the fulfilled Promise. The then
method takes a callback function that is called when the Promise is fulfilled. In this case, the callback function logs the new car object to the console.
If the Promise is rejected, we use the catch
method to handle the rejection. The catch
method takes a callback function that is called when the Promise is rejected. In this case, the callback function logs the error message to the console.
How is Factory pattern different from Abstract Factory pattern
The Factory pattern is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created. The Abstract Factory pattern is also a creational pattern, but it creates families of related objects without specifying their concrete classes.
The Factory pattern is simpler and more flexible, while the Abstract Factory pattern is more complex and more focused on creating families of objects.
Advantages and Disadvantages of the JavaScript Factory Pattern
One advantage of the Factory Pattern is that it can improve code organization and scalability. By centralizing the object creation logic, the pattern reduces code duplication and makes it easier to modify and maintain the code. Additionally, the pattern can improve performance by minimizing the number of objects created and reducing memory usage.
However, there are also potential drawbacks to using the Factory Pattern. For example, the pattern can increase the complexity of the code and make it more difficult to understand. Additionally, the pattern can reduce the flexibility of the code by limiting the types of objects that can be created.
Summary
The Factory Pattern is a useful design pattern that allows developers to create objects without having to specify their exact class. In JavaScript, this pattern can be implemented using different techniques such as using a function or constructor function to create objects with a common interface but different implementations.
By using the Factory Pattern, developers can simplify their code, avoid duplicating code, and provide a flexible interface for creating objects. This pattern can be especially useful when dealing with a large number of objects that have similar characteristics but different behaviors.
References
Factory method pattern - Wikipedia
The Factory Pattern - Learning JavaScript Design Patterns [Book] (oreilly.com)