Published on

Factory Method

Authors
  • avatar
    Name
    Khánh
    Twitter

Factory Method

Purpose of using the Factory Method:

  • Adding a new object does not affect the existing code (just create the object, create the factory, and execute, WITHOUT MODIFYING OLD CODE).
  • Classes are separated according to specific functions (can extend additional objects in their corresponding factories).
  • If there is only one factory for all Products, it is no longer the Factory Method but a Simple Factory.

Compared to Simple Factory:

  • The Factory Method pattern uses inheritance.
  • Subclasses (child Factories) have more control (can define their own way of creating products).
  • More flexible since each Factory is a separate class, not just a switch-case.

Steps to create and use the Factory Method:

  1. Create an abstract class for the "Product".
  2. Create classes for specific products.
  3. Create an abstract class for the "Factory".
  4. Create corresponding factories for the "Products".
  5. Use the objects created through the Factories instead of creating them directly with new "Product".

Examples

Without Factory Methods

When not using the factory method pattern:

  • Adding a new object "Triangle" requires modifying the main function's code to create the new object (increasing the risk of errors and reducing maintainability).
  • Difficulties arise if there are changes in logic or new requirements.
  • Hard to manage when the object undergoes complex changes.
interface Shape {
  draw(): void;
}

class Circle implements Shape {
  draw(): void {
    console.log("Drawing a Circle");
  }
}

class Square implements Shape {
  draw(): void {
    console.log("Drawing a Square");
  }
}

// new object
class Triangle implements Shape {
  draw(): void {
    console.log("Drawing a Triangle");
  }
}

function main() {
  const shapes: Shape[] = [];

  //  These code will be change if we add new objects.
  shapes.push(new Circle());
  shapes.push(new Square());
  shapes.push(new Triangle());

  shapes.forEach((shape) => shape.draw());
}

main();

Factory Methods Examples

//  1. Create an abstract class for the "Product".
abstract class Transport {
  name: string;
  deliver: () => void;
}

// 2. Create classes for specific products.
class Truck implements Transport {
  name: string;
  constructor(truckName: string) {
    this.name = truckName;
  }

  public deliver() {
    console.log(`${this.name} - running...`);
  }
}

class Ship implements Transport {
  name: string;
  constructor(shipName: string) {
    this.name = shipName;
  }

  public deliver() {
    console.log(`${this.name} - running...`);
  }
}

//  3. Create an abstract class for the "Factory".
abstract class Logistic {
  createTransport: () => Transport;
}

// 4. Create corresponding factories for the "Products".
class RoadLogistics implements Logistic {
  public createTransport() {
    return new Truck("Van");
  }
}

class SeaLogistics implements Logistic {
  public createTransport() {
    return new Ship("Ship");
  }
}

// 5. Use the objects created through the Factories instead of creating them directly with new "Product".
const roadLogistics = new RoadLogistics();
const truck = roadLogistics.createTransport();
truck.deliver();

const seaLogistics = new SeaLogistics();
const ship = seaLogistics.createTransport();
ship.deliver();

Simple Factory

  • Easier to implement.
  • Suitable for small systems or when the number of objects is not too large.
  • Difficult to extend because adding a new "Product" requires changing the code in the Factory class.
interface Shape {
  draw(): void;
}

// Define Simple Factory
class ShapeFactory {
  static createShape(type: string): Shape {
    switch (type) {
      case "circle":
        return new Circle();
      case "square":
        return new Square();
      case "triangle":
        return new Triangle();
      default:
        throw new Error("Unknown shape type");
    }
  }
}

function mainSimpleFactory() {
  const shapes: Shape[] = [];

  shapes.push(ShapeFactory.createShape("circle"));
  shapes.push(ShapeFactory.createShape("square"));
  shapes.push(ShapeFactory.createShape("triangle"));

  shapes.forEach((shape) => shape.draw());
}

mainSimpleFactory();

References