The Factory Design Pattern is a creational design pattern that provides a way to create objects without specifying the exact class of the object that will be created. It involves creating a factory method that decides which class to instantiate based on the input or configuration. It is used when all the object creation and its business logic we need to keep at one place.
The main advantage factory design pattern is its ability to decouple the creation of an object from one particular implementation.
It allows to create object whose class is determined at runtime.
Factory allows us to expose a "surface area" that is much smaller than that of a class, class can be extended, manipulated, while factory being just a function offers fewer options to the user, making it more robust.
Hence factory can also be used to enforce encapsulation by leveraging closures.
A method to impose encapsulation
In Javascript, one of the main ways to enforce encapsulation is through functions scopes and closures.
A factory can also be used as an encapsulation mechanism.
Encapsulation refers to controlling the access to some internal details of the component by preventing external code from manipulating them directly. The interaction with the component happen only through its public interface, isolating external code from the changes in the implementation details of the component.
Closures allow you to create private variables and methods that are not accessible from outside the factory, thereby enforcing encapsulation and hiding the internal details of object creation and implementation.
Decoupling object creation and implementation
Invoking a factory, instead of directly creating new object from a class using the new operator or Object.create()
, is so much more convenient and flexible in several respects.
Factory allows us to separate the creation of an object from the implementation. A factory wraps the creation of a new instance, giving us more flexibility and control in a way we do it. Inside the factory we choose to create a new instance of a class using the new operator, or leverage closures to dynamically build a stateful object literal, or even return a different object type based on a particular condition. The consumer of factory is totally unaware of how the creation of instance is carried out.
Let's take small example to understand why we need factory design pattern
function createImg(name) {
return new Image(name);
}
const image = createImg('photo.jpg');
You could have said why to write this extra line of code , when we can directly write:
const image = new Image(name);
So the idea behind using a factory function (createImg) is to abstract away the process of creating an object.
Benefits of Factory Function in Refactoring:
Single Point of Change: By using a factory function, you centralize the object creation process. Refactoring or extending logic requires changes in one place rather than throughout the codebase.
Simplifies Client Code: Client code that consumes the factory function remains unchanged, even as the complexity of the object creation process increases.
Encapsulation: The factory function encapsulates any additional logic (e.g., caching, default parameters, or new object types). This prevents duplication of logic in multiple places and reduces the risk of errors during refactoring.
Maintainability: As your code grows, maintaining a factory function becomes much easier than refactoring direct instantiation. With a factory function, you can introduce new features, make optimizations, or fix bugs without affecting the rest of the code.
Example
Here’s a basic example of implementing the Factory Design Pattern in JavaScript:
Scenario: A factory for creating different types of vehicles (Car, Bike, Truck), depending on the input.
// Vehicle constructor functions
class Car {
constructor(brand, model) {
this.vehicleType = 'Car';
this.brand = brand;
this.model = model;
}
drive() {
return `Driving a ${this.brand} ${this.model} car.`;
}
}
class Bike {
constructor(brand, model) {
this.vehicleType = 'Bike';
this.brand = brand;
this.model = model;
}
ride() {
return `Riding a ${this.brand} ${this.model} bike.`;
}
}
class Truck {
constructor(brand, model) {
this.vehicleType = 'Truck';
this.brand = brand;
this.model = model;
}
haul() {
return `Hauling with a ${this.brand} ${this.model} truck.`;
}
}
// Vehicle factory that creates vehicles based on type
class VehicleFactory {
static createVehicle(type, brand, model) {
switch (type) {
case 'car':
return new Car(brand, model);
case 'bike':
return new Bike(brand, model);
case 'truck':
return new Truck(brand, model);
default:
throw new Error('Vehicle type not supported.');
}
}
}
// Using the factory to create vehicles
const myCar = VehicleFactory.createVehicle('car', 'Tesla', 'Model 3');
console.log(myCar.drive()); // Output: Driving a Tesla Model 3 car.
const myBike = VehicleFactory.createVehicle('bike', 'Yamaha', 'MT-15');
console.log(myBike.ride()); // Output: Riding a Yamaha MT-15 bike.
const myTruck = VehicleFactory.createVehicle('truck', 'Ford', 'F-150');
console.log(myTruck.haul()); // Output: Hauling with a Ford F-150 truck.
How It Works:
- Vehicle Classes: We define different types of vehicles (Car, Bike, Truck), each with its own constructor and specific methods like drive(), ride(), and haul().
- Factory Method: The VehicleFactory.createVehicle() method is the factory that handles the logic of creating objects. It decides which type of vehicle to instantiate based on the type argument passed to it.
- Reusability: The factory centralizes the logic for creating vehicles, making it easy to manage, extend, or modify the creation process.
Ps: In the Factory Design Pattern examples above, classes like Car, Bike, and Truck are instantiated using the new keyword inside the factory method (VehicleFactory.createVehicle)
The Factory Pattern abstracts object creation, meaning the client doesn't have to use the new keyword themselves. They rely on the factory method to return the correct instance.
When to Use the Factory Pattern:
- When the exact types of objects need to be determined at runtime.
- When you want to centralize the object creation logic.
- When the creation process involves complex logic or multiple steps.
Summary
- The Factory Design Pattern is a useful way to handle complex or varied object creation in JavaScript.
- It abstracts the instantiation logic, allowing for flexibility and easier management of different object types.
- This pattern is widely used in real-world applications, especially when working with complex systems where object creation may depend on runtime conditions or configurations.
- In a real-world project, as your code evolves and becomes more complex, the factory function approach minimizes the number of changes you need to make, making your code more maintainable and easier to refactor.
Reference Book : NodeJs Design pattern by Mario Casciaro
As we've explored, design patterns play a crucial role in solving common software design challenges efficiently. Whether you're just beginning like I am, or looking to deepen your understanding, the insights shared here can help you build more adaptable and scalable systems.
The journey to mastering design patterns may feel overwhelming at first, but by starting small, experimenting, and applying these concepts in real-world projects, you'll strengthen your skills as a developer. Now it's your turn! How will you apply these ideas to your work? Share your thoughts or questions in the comments below—I’d love to hear from you.
Thank you for joining me on this learning journey!
✨✨✨✨✨✨
Top comments (0)