Les Fondamentaux 🌟
Introduction au Pattern Singleton
Imaginez un super-héros unique, qui ne peut exister qu'en un seul exemplaire dans l'univers. C'est exactement ça, le patron de conception Singleton ! 🦸♂️
Pourquoi avoir besoin d'un héros unique ? 🤔
Voici des scénarios courants :
- 🎮 Une partie de jeu vidéo avec plusieurs tableaux des scores... la confusion totale !
- 🏦 Plusieurs connexions à la même base de données... quel gâchis de ressources !
- ⚙️ Plusieurs configurations différentes... c'est la porte ouverte au chaos !
Super-pouvoirs du Singleton
- 💪 Force d'Unicité : Une seule instance, comme un seul Batman pour Gotham
- 🌟 Vision Globale : Accessible de partout, comme le Bat-Signal dans le ciel
- 🎯 Précision : Un seul point de vérité, comme l'unique anneau de Sauron
Implémentation Basique
Le code de notre héros
1class Logger {
2 private static instance: Logger;
3 private logCount: number = 0;
4
5 private constructor() {
6 console.log('🚀 Logger awakens...');
7 }
8
9 public static getInstance(): Logger {
10 if (!Logger.instance) {
11 Logger.instance = new Logger();
12 }
13 return Logger.instance;
14 }
15
16 public log(message: string): void {
17 this.logCount++;
18 console.log(`📝 [Log #${this.logCount}] ${message}`);
19 }
20
21 public getStats(): string {
22 return `📊 Total logs: ${this.logCount}`;
23 }
24}
Utilisation Simple
1const logger = Logger.getInstance();
2logger.log('Notre héros est prêt !');
3console.log(logger.getStats());
Techniques Avancées 🚀
Singleton Thread-Safe
Dans un environnement multi-thread, notre héros a besoin d'une armure supplémentaire ! Voici une implémentation thread-safe :
1class ThreadSafeLogger {
2 private static instance: ThreadSafeLogger;
3 private static instanceLock = false;
4 private logs: string[] = [];
5
6 private constructor() {}
7
8 public static getInstance(): ThreadSafeLogger {
9 if (!ThreadSafeLogger.instanceLock) {
10 ThreadSafeLogger.instanceLock = true;
11 if (!ThreadSafeLogger.instance) {
12 ThreadSafeLogger.instance = new ThreadSafeLogger();
13 }
14 ThreadSafeLogger.instanceLock = false;
15 }
16 return ThreadSafeLogger.instance;
17 }
18}
Singleton avec Initialisation Lazy 🦥
1class LazyLogger {
2 private static instance: LazyLogger;
3 private config: object;
4
5 private constructor(config: object) {
6 this.config = config;
7 }
8
9 public static getInstance(config?: object): LazyLogger {
10 if (!LazyLogger.instance && config) {
11 LazyLogger.instance = new LazyLogger(config);
12 }
13 if (!LazyLogger.instance) {
14 throw new Error('Logger needs configuration on first initialization!');
15 }
16 return LazyLogger.instance;
17 }
18}
Singleton avec Reset pour les Tests 🧪
1class TestableLogger {
2 private static instance: TestableLogger;
3
4 private constructor() {}
5
6 public static getInstance(): TestableLogger {
7 if (!TestableLogger.instance) {
8 TestableLogger.instance = new TestableLogger();
9 }
10 return TestableLogger.instance;
11 }
12
13 // Méthode spéciale pour les tests
14 public static resetInstance(): void {
15 TestableLogger.instance = null;
16 }
17}
Singleton Générique 🎭
1class GenericSingleton<T> {
2 private static instances: Map<string, any> = new Map();
3
4 protected constructor() {}
5
6 public static getInstance<T>(this: new () => T): T {
7 const className = this.name;
8 if (!GenericSingleton.instances.has(className)) {
9 GenericSingleton.instances.set(className, new this());
10 }
11 return GenericSingleton.instances.get(className);
12 }
13}
14
15// Utilisation
16class UserService extends GenericSingleton<UserService> {
17 public getUsers() {
18 return ['user1', 'user2'];
19 }
20}
21
22class ConfigService extends GenericSingleton<ConfigService> {
23 public getConfig() {
24 return { api: 'url' };
25 }
26}
Anti-Patterns et Pièges à Éviter ⚠️
- Couplage Fort
1// À éviter ❌
2class BadSingleton {
3 public static getInstance() {
4 // Code...
5 }
6 public doDirectDatabaseOperation() {
7 // Opération directe sur la DB
8 }
9}
10
11// Préférer ✅
12interface DatabaseOperation {
13 execute(): void;
14}
15
16class GoodSingleton {
17 public static getInstance() {
18 // Code...
19 }
20 public executeOperation(operation: DatabaseOperation) {
21 operation.execute();
22 }
23}
- État Mutable Global
1// À éviter ❌
2class MutableSingleton {
3 private static instance: MutableSingleton;
4 public globalState: any = {};
5}
6
7// Préférer ✅
8class ImmutableSingleton {
9 private static instance: ImmutableSingleton;
10 private state: Readonly<any>;
11
12 public getState(): Readonly<any> {
13 return this.state;
14 }
15}
Patterns Complémentaires 🤝
Factory + Singleton
1interface Logger {
2 log(message: string): void;
3}
4
5class LoggerFactory {
6 private static instance: LoggerFactory;
7 private loggers: Map<string, Logger> = new Map();
8
9 private constructor() {}
10
11 public static getInstance(): LoggerFactory {
12 if (!LoggerFactory.instance) {
13 LoggerFactory.instance = new LoggerFactory();
14 }
15 return LoggerFactory.instance;
16 }
17
18 public getLogger(type: 'console' | 'file'): Logger {
19 if (!this.loggers.has(type)) {
20 this.loggers.set(type, this.createLogger(type));
21 }
22 return this.loggers.get(type);
23 }
24
25 private createLogger(type: 'console' | 'file'): Logger {
26 // Création du logger spécifique
27 return type === 'console' ? new ConsoleLogger() : new FileLogger();
28 }
29}
Challenge Avancé 🎯
Créez un Singleton qui :
- Est thread-safe
- Utilise l'initialisation lazy
- Permet le reset pour les tests
- Intègre un système de versioning
Pour Finir 🎬
Le Singleton est un pattern puissant mais qui demande de la réflexion dans son implémentation. La version basique suffit pour des cas simples, mais les versions avancées offrent plus de robustesse et de flexibilité pour des applications complexes.
Pro Tips 💡
- Utilisez l'initialisation lazy pour les ressources coûteuses
- Pensez toujours à la testabilité
- Préférez l'injection de dépendances quand possible
- Documentez bien les cas d'utilisation de vos Singletons
Exercices Pratiques 🎮
- Implémentez un Singleton pour gérer les configurations d'une application
- Créez un Singleton thread-safe avec une file d'attente de messages
- Développez un système de cache utilisant le pattern Singleton
Happy coding! 🚀