← Back to Home

Factory Method Pattern

Define an interface for creating an object, but let subclasses decide which class to instantiate.

What is it?

Factory Method provides **extensibility without modification** - you can add new types without changing existing creation code.

Demo: The Extensibility Problem

Imagine you built an app in **v1.0** with Circle and Square. Now in **v2.0**, you need Triangle and Star. Watch what happens to each approach:

Settings:

🏭 Factory Method (v2.0)

Supports all shapes gracefully

Result:
Click a button to create shapes...
Creation Log:
No activity yet...

🔧 Direct Instantiation (v1.0)

Old hardcoded logic - breaks with new shapes!

Result:
Click a button to create shapes...
Creation Log:
No activity yet...

💡 The Key Difference:

  • • **Factory Method**: All 4 shapes work perfectly - handles extension gracefully
  • • **Direct Instantiation**: Only 2 shapes work - breaks when new types are added
  • • Try clicking Triangle and Star to see the dramatic difference!

Code Example: The Real Problem

// Factory Method - Change ONE place, works everywhere
class ShapeFactory {
  static createShape(type: string): Shape {
    switch (type) {
      case 'circle': return new Circle();
      case 'square': return new Square();
      case 'triangle': return new Triangle(); // Add once here
      case 'star': return new Star();         // Add once here
      default: throw new Error(`Unknown: ${type}`);
    }
  }
}

// All these classes use the factory - NO changes needed:
class DrawingCanvas {
  createShape(type: string) {
    return ShapeFactory.createShape(type); // Works automatically
  }
}

class GameEngine {
  spawnEnemy(type: string) {
    return ShapeFactory.createShape(type); // Works automatically  
  }
}

class UIBuilder {
  buildIcon(type: string) {
    return ShapeFactory.createShape(type); // Works automatically
  }
}

// vs Direct Instantiation - Change EVERY place manually
class DrawingCanvas {
  createShape(type: string): Shape {
    switch (type) {
      case 'circle': return new Circle();
      case 'square': return new Square();
      // Must add Triangle here manually!
      // Must add Star here manually!
      default: throw new Error('Forgot to update this one!');
    }
  }
}

class GameEngine {
  spawnEnemy(type: string): Shape {
    switch (type) {
      case 'circle': return new Circle();
      case 'square': return new Square();
      // Must add Triangle here manually!
      // Must add Star here manually!
      default: throw new Error('Forgot to update this one!');
    }
  }
}

class UIBuilder {
  buildIcon(type: string): Shape {
    switch (type) {
      case 'circle': return new Circle();
      case 'square': return new Square();
      // Must add Triangle here manually!
      // Must add Star here manually!
      default: throw new Error('Forgot to update this one!');
    }
  }
}

// And 12 other classes with duplicated creation logic...

// RESULT:
// Factory Method: 1 change → works everywhere
// Direct Instantiation: 15 changes → high chance of missing some

Common Uses

  • Plugin systems where new types can be added without changing core code
  • UI widget libraries that support extensible component types
  • Database drivers that can be extended with new database types
  • Export systems supporting multiple formats (PDF, Excel, CSV...)

When to Use

  • When you expect to add new types in the future
  • When you want to avoid modifying existing creation code
  • When creation logic might become complex or vary by type
  • When you want to centralize object creation logic

Caution

  • Can add complexity for simple scenarios that rarely change
  • Still requires modifying the factory when adding new types
  • May be overkill if you only have 2-3 stable types