The Factory Design Pattern is a creational design pattern that provides a way to create objects without specifying the exact class of object that will be created. Instead of calling a constructor directly, the Factory pattern defines a method for creating objects, allowing subclasses or external code to decide which class to instantiate based on some logic or configuration.
In JavaScript, the Factory pattern is often implemented
using functions or classes. The main idea is to have a factory function
or factory class that returns an instance of an object.
Key Characteristics:
- Object
Creation Abstraction: The Factory pattern abstracts the creation
process of an object and provides a more flexible way to instantiate
objects.
- Loose
Coupling: It decouples the client code from the specific class being
instantiated, which is useful when you want to introduce new types without
changing the code that uses the factory.
Use Cases:
- When
the exact type of object to create isn't known until runtime.
- When
the creation of an object involves complex logic that shouldn't be handled
directly by the client.
- When
you need to create different types of objects that share a common
interface but have different implementations.
Example of Factory Pattern in JavaScript
Let's create a Shape Factory that returns different
shape objects based on the type provided.
1. Factory Function Example
// Shape Factory
function ShapeFactory(type) {
if (type === 'circle')
{
return new Circle();
} else if (type === 'square')
{
return new Square();
} else {
throw new Error("Invalid
shape type");
}
}
// Circle class
class Circle {
draw() {
console.log("Drawing
a circle");
}
}
// Square class
class Square {
draw() {
console.log("Drawing
a square");
}
}
// Usage
const circle = ShapeFactory('circle');
circle.draw(); //
Output: Drawing a circle
const square = ShapeFactory('square');
square.draw(); //
Output: Drawing a square
Explanation:
- The ShapeFactory
is a factory function that takes a type as an argument and returns the
appropriate shape object (Circle or Square).
- The Circle
and Square classes implement a common draw method, but they have
different implementations.
- The
client doesn't need to know the details about how to create a shape
object; it simply calls the factory method and gets the correct shape.
2. Factory Class Example
Alternatively, you can implement the Factory pattern using a
class.
// Shape Factory Class
class ShapeFactory {
static createShape(type)
{
if (type === 'circle')
{
return new Circle();
} else if (type
=== 'square') {
return new Square();
} else {
throw new Error("Invalid
shape type");
}
}
}
// Circle class
class Circle {
draw() {
console.log("Drawing
a circle");
}
}
// Square class
class Square {
draw() {
console.log("Drawing
a square");
}
}
// Usage
const circle = ShapeFactory.createShape('circle');
circle.draw(); //
Output: Drawing a circle
const square = ShapeFactory.createShape('square');
square.draw(); //
Output: Drawing a square
Explanation:
- The ShapeFactory
class has a static method createShape which returns the correct shape
based on the type.
- This
approach uses a class-based pattern, but the concept remains the
same: encapsulating the object creation logic in a central place, making
it easier to manage and extend.
Benefits of the Factory Design Pattern:
- Decouples
Object Creation: The client code does not need to know about the
instantiation process of the objects. This allows for flexibility and
easier maintenance.
- Flexibility
to Change Object Creation Logic: You can change how objects are
created without modifying the code that uses them.
- Easier
to Extend: Adding new types of objects (like a new shape in the
example) does not require changes to the client code that uses the
factory.
- Encapsulates
Complex Creation Logic: If the process of creating an object is
complex (involving conditions, validations, or dependency injection), the
factory can encapsulate that logic and simplify client code.
Drawbacks:
- Potential
for Overhead: If overused or misused, it can lead to unnecessary
layers of abstraction, complicating the design.
- Limited
to Object Creation: Factory pattern is mainly focused on object
creation. If your application doesn't require a variety of objects or the
creation logic is simple, a factory may not be necessary.
When to Use the Factory Pattern:
- When
you want to create different types of objects based on configuration or
user input, but you want to abstract away the complexity of creating them.
- When
an object creation process requires conditional logic or setup that
shouldn't be in the client code.
- When
you want to simplify object creation in large systems with multiple object
types that share a common interface or behavior.
Factory Pattern with Dependency Injection Example:
A real-world example where you might use the Factory pattern
with dependency injection is when you need to create objects with
certain dependencies (like a service or data source).
class Service {
constructor(db) {
this.db = db; //
Dependency Injection
}
fetchData() {
return this.db.getData();
}
}
class Database {
getData() {
return "Data
from DB";
}
}
class ServiceFactory {
static createService()
{
const db = new Database();
return new Service(db);
}
}
const service = ServiceFactory.createService();
console.log(service.fetchData()); // Output: Data from DB
Explanation:
- The Service
class depends on the Database class, and the ServiceFactory class creates
an instance of Service with its dependency (Database).
- This
example shows how you can use a factory to instantiate classes with
dependencies, making it easier to manage the creation process and inject
required dependencies.
Conclusion:
The Factory Design Pattern is a useful pattern when
you need to abstract and centralize the process of creating objects. It makes
object creation more flexible, decouples client code from specific
implementations, and allows for easier extension and maintenance.
No comments:
Post a Comment