- Published on
Factory Method
- Authors
- Name
- Khánh
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:
- Create an abstract class for the "Product".
- Create classes for specific products.
- Create an abstract class for the "Factory".
- Create corresponding factories for the "Products".
- 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();