Monday, 2 December 2024

Overview of Factory Design Pattern in JavaScript

 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:

  1. 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.

  2. Flexibility to Change Object Creation Logic: You can change how objects are created without modifying the code that uses them.

  3. 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.

  4. 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:

  1. Potential for Overhead: If overused or misused, it can lead to unnecessary layers of abstraction, complicating the design.

  2. 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.

Bottom of Form

 

No comments:

Post a Comment