Singleton Alternatives
Dependency Injection (Preferred)
Instead of global access, inject dependencies:
typescript1// Instead of Singleton2class OrderService {3 process() {4 const config = Configuration.getInstance(); // Hidden dependency5 // ...6 }7}89// With Dependency Injection10class OrderService {11 constructor(private config: Configuration) {} // Explicit dependency1213 process() {14 const apiKey = this.config.get('apiKey');15 // ...16 }17}1819// Configure at application startup20const config = new Configuration();21const orderService = new OrderService(config);
IoC Container
typescript1interface Container {2 register<T>(token: string, instance: T): void;3 resolve<T>(token: string): T;4}56class SimpleContainer implements Container {7 private instances = new Map<string, unknown>();89 register<T>(token: string, instance: T): void {10 this.instances.set(token, instance);11 }1213 resolve<T>(token: string): T {14 const instance = this.instances.get(token);15 if (!instance) throw new Error(`No instance for ${token}`);16 return instance as T;17 }18}1920// Usage21const container = new SimpleContainer();22container.register('config', new Configuration());23container.register('logger', new Logger());2425// Resolve where needed26const config = container.resolve<Configuration>('config');
When Singleton is Still Useful
- Logger (cross-cutting concern)
- Configuration (truly global)
- Object pools
- Rate limiters
Testing with Singletons
typescript1// Provide reset for testing2class Configuration {3 private static instance: Configuration | null = null;45 static getInstance(): Configuration {6 if (!Configuration.instance) {7 Configuration.instance = new Configuration();8 }9 return Configuration.instance;10 }1112 // For testing only13 static resetInstance(): void {14 Configuration.instance = null;15 }16}
Summary
Prefer Dependency Injection over Singleton when possible. It makes dependencies explicit and testing easier.