Chapitre 2
Chapitre 02 — Patterns de Création : Singleton
Chapitre 02 — Patterns de Création : Singleton
Cours 02 — Patterns de Création : Singleton, Factory Method, Abstract Factory
Partie 1 : Singleton
1.1 Définition
Le Singleton garantit qu'une classe n'a qu'une seule instance et fournit un point d'accès global à cette instance.
Diagramme en cours de génération...
1.2 Problème résolu
Certaines ressources doivent être partagées dans toute l'application :
- Configuration globale
- Pool de connexions à la base de données
- Gestionnaire de logs
- Cache applicatif
// Usage typique
const config = Config.getInstance();
config.set("apiKey", "abc123");
// Même instance
const sameConfig = Config.getInstance();
console.log(config === sameConfig); // true
1.3 Implémentations
Version simple (non thread-safe)
class Config {
private static instance: Config | null = null;
private settings: Map<string, string> = new Map();
private constructor() {}
static getInstance(): Config {
if (!Config.instance) {
Config.instance = new Config();
}
return Config.instance;
}
set(key: string, value: string): void {
this.settings.set(key, value);
}
get(key: string): string | undefined {
return this.settings.get(key);
}
}
Version thread-safe (TypeScript)
class Config {
private static instance: Config;
private static mutex: Promise<void> = Promise.resolve();
private settings: Map<string, string> = new Map();
private constructor() {}
static async getInstance(): Promise<Config> {
if (!Config.instance) {
Config.instance = new Config();
}
return Config.instance;
}
}
Version avec once (Go)
package config
import "sync"
var (
instance *Config
once sync.Once
)
type Config struct {
settings map[string]string
mu sync.RWMutex
}
func GetInstance() *Config {
once.Do(func() {
instance = &Config{
settings: make(map[string]string),
}
})
return instance
}
Version avec énumération (Java)
public enum Config {
INSTANCE;
private final Map<String, String> settings = new HashMap<>();
public void set(String key, String value) {
settings.put(key, value);
}
public String get(String key) {
return settings.get(key);
}
}
1.4 Problèmes du Singleton
Diagramme en cours de génération...
| Problème | Description | Solution |
|---|---|---|
| Testabilité | L'état global persiste entre les tests | Injection de dépendance |
| Couplage | Les clients appellent getInstance() directement | Interface + DI |
| Concurrence | Accès simultané à l'état | Mutex/atomic |
| Cycle de vie | L'instance vit jusqu'à la fin du processus | Permettre la réinitialisation |
| Global state | État caché, difficile à tracer | Journalisation |
1.5 Quand utiliser le Singleton ?
Oui, c'est approprié :
- Logger (écriture sérialisée)
- Horloge système
- Gestionnaire de tâches planifiées
- Cache read-heavy
Non, préférez autre chose :
- Base de données (préférez un pool)
- Configuration (peut être injectée)
- Services métier (DI)
Partie 2 : Factory Method
2.1 Définition
La Factory Method définit une interface pour créer un objet, mais laisse les sous-classes décider quelle classe instancier.
Diagramme en cours de génération...
2.2 Problème résolu
Une classe ne peut pas anticiper le type d'objets qu'elle doit créer. La Factory Method délègue cette décision aux sous-classes.
2.3 Exemple : Générateur de documents
interface Document {
open(): void;
save(): void;
close(): void;
}
class WordDocument implements Document {
open(): void { console.log("Opening Word document"); }
save(): void { console.log("Saving Word document"); }
close(): void { console.log("Closing Word document"); }
}
class PDFDocument implements Document {
open(): void { console.log("Opening PDF document"); }
save(): void { console.log("Saving PDF document"); }
close(): void { console.log("Closing PDF document"); }
}
abstract class Application {
abstract createDocument(): Document;
newDocument(): void {
const doc = this.createDocument();
doc.open();
doc.save();
doc.close();
}
}
class WordApplication extends Application {
createDocument(): Document {
return new WordDocument();
}
}
class PDFApplication extends Application {
createDocument(): Document {
return new PDFDocument();
}
}
// Usage
const app: Application = new WordApplication();
app.newDocument(); // Template Method pattern
2.4 Lien avec Template Method
La Factory Method est souvent appelée dans une Template Method :
abstract class DataExporter {
// Template Method
export(data: any): void {
const formatted = this.format(data);
const compressed = this.compress(formatted);
this.write(compressed);
}
protected abstract format(data: any): string;
protected abstract compress(data: string): Buffer;
protected abstract write(data: Buffer): void;
}
Partie 3 : Abstract Factory
3.1 Définition
L'Abstract Factory fournit une interface pour créer des familles d'objets apparentés sans spécifier leurs classes concrètes.
Diagramme en cours de génération...
3.2 Exemple : UI Toolkit multi-plateforme
interface Button {
render(): void;
onClick(callback: () => void): void;
}
interface Checkbox {
render(): void;
toggle(): void;
}
interface GUIFactory {
createButton(): Button;
createCheckbox(): Checkbox;
}
// Windows
class WinButton implements Button {
render(): void { console.log("Windows Button"); }
onClick(callback: () => void): void { /* Win event */ }
}
class WinCheckbox implements Checkbox {
render(): void { console.log("Windows Checkbox"); }
toggle(): void { /* Win toggle */ }
}
class WinFactory implements GUIFactory {
createButton(): Button { return new WinButton(); }
createCheckbox(): Checkbox { return new WinCheckbox(); }
}
// macOS
class MacButton implements Button {
render(): void { console.log("macOS Button"); }
onClick(callback: () => void): void { /* Mac event */ }
}
class MacCheckbox implements Checkbox {
render(): void { console.log("macOS Checkbox"); }
toggle(): void { /* Mac toggle */ }
}
class MacFactory implements GUIFactory {
createButton(): Button { return new MacButton(); }
createCheckbox(): Checkbox { return new MacCheckbox(); }
}
// Application
class Application {
private button: Button;
private checkbox: Checkbox;
constructor(factory: GUIFactory) {
this.button = factory.createButton();
this.checkbox = factory.createCheckbox();
}
render(): void {
this.button.render();
this.checkbox.render();
}
}
// Usage
const app = new Application(new WinFactory());
app.render();
Partie 4 : Comparaison
| Critère | Singleton | Factory Method | Abstract Factory |
|---|---|---|---|
| Objectif | Instance unique | Déléguer la création | Familles d'objets |
| Scope | Une classe | Une hiérarchie de classes | Plusieurs hiérarchies |
| Variation | Point d'accès | Type d'objet créé | Famille complète |
| Testabilité | Faible | Bonne | Bonne |
| Complexité | Faible | Moyenne | Élevée |
Comment choisir ?
Diagramme en cours de génération...
Anti-pattern associé
Singletonitis : utilisation excessive du Singleton pour tout ce qui est "unique", créant un état global non maîtrisé.
Solution : préférer l'injection de dépendance et les instances uniques gérées par le conteneur DI (un "Singleton" du point de vue de l'application, mais pas du pattern).