MFormations
Modern Design Patterns

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èmeDescriptionSolution
TestabilitéL'état global persiste entre les testsInjection de dépendance
CouplageLes clients appellent getInstance() directementInterface + DI
ConcurrenceAccès simultané à l'étatMutex/atomic
Cycle de vieL'instance vit jusqu'à la fin du processusPermettre la réinitialisation
Global stateÉtat caché, difficile à tracerJournalisation

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èreSingletonFactory MethodAbstract Factory
ObjectifInstance uniqueDéléguer la créationFamilles d'objets
ScopeUne classeUne hiérarchie de classesPlusieurs hiérarchies
VariationPoint d'accèsType d'objet crééFamille complète
TestabilitéFaibleBonneBonne
ComplexitéFaibleMoyenneÉ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).