Master the art of creating objects without tight coupling to concrete classes
When you use new everywhere, your code becomes rigid:
// Client code tightly coupled to concrete classes
function processPayment(type: string, amount: number) {
let processor
if (type === 'credit') {
processor = new CreditCardProcessor()
} else if (type === 'paypal') {
processor = new PayPalProcessor()
} else if (type === 'crypto') {
processor = new CryptoProcessor()
}
return processor.process(amount)
}
What's wrong? Adding new payment types requires modifying this function.
Factory Method defines an interface for creating objects, but lets subclasses decide which class to instantiate.
Key Benefits:
// Every module that needs a logger creates it directly
class UserService {
private logger = new ConsoleLogger() // Coupled!
createUser(data: UserData) {
this.logger.log('Creating user...')
// ...
}
}
class OrderService {
private logger = new ConsoleLogger() // Duplicated!
processOrder(order: Order) {
this.logger.log('Processing order...')
// ...
}
}
Problems: Hard to change logger type, difficult to test, repeated instantiation
// Logger interface
interface ILogger {
log(message: string): void
}
// Factory creates loggers based on environment
class LoggerFactory {
static create(): ILogger {
const env = process.env.NODE_ENV
if (env === 'production') {
return new CloudLogger()
} else if (env === 'development') {
return new ConsoleLogger()
}
return new FileLogger()
}
}
// Services now depend on abstraction
class UserService {
private logger = LoggerFactory.create()
// ...
}
Better: Centralized creation, easy to test, flexible configuration
Creating platform-specific UI elements (iOS vs Android vs Web)
Word, PDF, Excel exporters with a common interface
Credit card, PayPal, crypto, bank transfer processors
MySQL, PostgreSQL, MongoDB connectors based on config
Email, SMS, push, in-app notifications
Pattern: When you have multiple implementations of a common interface
new ClassName())Rule of thumb: Don't use patterns just because they exist. Use them when they solve a real problem.
// Just a function/class that creates objects
class ShapeFactory {
static create(type: string): Shape {
if (type === 'circle') return new Circle()
if (type === 'square') return new Square()
throw new Error('Unknown type')
}
}
Not a formal pattern. Just a helper function.
// Uses inheritance - subclasses decide what to create
abstract class ShapeCreator {
abstract createShape(): Shape
render() {
const shape = this.createShape()
shape.draw()
}
}
class CircleCreator extends ShapeCreator {
createShape(): Shape {
return new Circle()
}
}
Formal pattern. Uses inheritance and polymorphism.
Remember: The goal is cleaner, more maintainable code—not just applying patterns for the sake of it.
Be ready to explain the difference between Simple Factory, Factory Method, and Abstract Factory. Most real-world code uses Simple Factory because it's simpler!