← 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 someCommon 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