Explorez le pattern Singleton : quand l'utiliser, exemples pratiques en TypeScript et alternatives essentielles.
January 19, 2026 (3mo ago)
Maîtriser le pattern Singleton : guide complet du développeur
Explorez le pattern Singleton : quand l'utiliser, exemples pratiques en TypeScript et alternatives essentielles.
← Back to blog
Maîtriser le pattern Singleton en TypeScript : Guide complet

Dans le monde du développement logiciel, certains outils sont puissants mais doivent être maniés avec précaution. Le pattern Singleton en fait partie. Au fond, c'est un concept simple : garantir qu'une classe n'a qu'une seule instance et fournir un moyen global unique d'y accéder.1
Pensez-y comme à un gestionnaire de configuration central ou à un service de journalisation dédié pour l'ensemble de votre application. Vous ne voudriez pas d'objets de configuration multiples et contradictoires en circulation, ni voir les entrées de log éparpillées dans différents fichiers par des instances de logger concurrentes. Le Singleton apporte de l'ordre en empêchant ces duplications, économisant de la mémoire et évitant le chaos. C'est un pattern fondamental pour gérer l'accès à des ressources partagées.
Qu'est-ce que le pattern Singleton et quand est-il utile ?
Imaginez un royaume médiéval avec un seul Scribe royal officiel. Cette personne est la seule autorisée à consigner les décrets royaux. Cela garantit que chaque loi et annonce est cohérente, dûment autorisée et stockée dans un registre unique et définitif. Si n'importe qui pouvait décider d'être scribe, le royaume plongerait rapidement dans le chaos avec des enregistrements contradictoires et une confusion générale.
Le pattern de conception Singleton fonctionne selon ce même principe au sein de votre logiciel. Sa mission principale est de restreindre une classe afin qu'un seul objet puisse jamais en être créé. Cette instance unique devient la source de vérité pour une tâche spécifique, et elle est facilement accessible depuis n'importe quel point de votre base de code. C'est ainsi que vous imposez un contrôle sur des ressources qui ne doivent jamais être dupliquées.
But principal et analogie
Le Singleton ne consiste pas seulement à empêcher la création de nouveaux objets ; il s'agit de centraliser le contrôle. Tout comme le Scribe royal fournit un point d'accès unique aux registres officiels du royaume, une instance Singleton offre une passerelle universellement disponible vers une ressource partagée. Il empêche différentes parties de votre application de créer leurs propres versions isolées et potentiellement contradictoires.
Un pool de connexions à la base de données est un exemple classique. Vous ne voulez certainement pas que chaque composant de votre application ouvre sa propre connexion séparée à la base de données — c'est une façon sûre d'épuiser les ressources du serveur et de faire chuter les performances. À la place, un Singleton peut gérer un seul pool de connexions, les distribuant efficacement selon les besoins.
L'idée centrale est simple mais puissante : une classe, une instance, un point d'accès global. Cette structure garantit que toutes les interactions avec une ressource spécifique passent par un seul canal contrôlé.
Le pattern Singleton en un coup d'œil
| Caractéristique | Description & justification |
|---|---|
| Instance unique | La classe est conçue pour n'avoir qu'une seule instance pendant le cycle de vie de l'application, souvent appliquée avec un constructeur privé. |
| Point d'accès global | Une méthode statique (par ex. getInstance()) fournit un moyen unique et bien connu d'accéder à l'instance depuis n'importe où dans le code. |
| Initialisation paresseuse | L'instance unique est souvent créée la première fois qu'on la demande, pas au démarrage de l'application, ce qui peut améliorer les performances. |
| Gestion d'état | Il sert d'emplacement centralisé pour une partie spécifique de l'état global, comme les paramètres de l'application ou une session utilisateur. |
Ce tableau résume proprement pourquoi le pattern existe : faire respecter une instance unique et globalement accessible pour des ressources qui sont intrinsèquement singulières.
Cas d'utilisation pratiques
Bien que le pattern Singleton ait ses détracteurs, il n'est pas dépourvu d'utilisations légitimes. Il est le plus efficace quand vous avez une ressource qui est, par nature, unique au sein du système.
Voici quelques scénarios où un Singleton a du sens :
- Services de journalisation : une seule instance de logger garantit que tous les événements aboutissent dans le même fichier ou flux.
- Gestion de configuration : une source unique pour les paramètres de l'application évite les incohérences entre modules.
- Accès à une interface matérielle : une interface unique vers un périphérique empêche les commandes conflictuelles.
Les avantages et inconvénients d'utiliser des Singletons

Le pattern Singleton peut sembler être un outil fiable quand vous avez besoin d'un point d'accès unique à une ressource partagée. Il vous donne un moyen simple de gérer des éléments comme un objet de configuration ou un service de journalisation à travers toute votre application.
Avantages des Singletons
- Point d'accès global qui simplifie l'utilisation entre modules.
- Conservation des ressources grâce à l'initialisation paresseuse, ce qui peut réduire le coût au démarrage.
- Réduction des duplications empêchant plusieurs instances conflictuelles de ressources coûteuses.
Inconvénients des Singletons
- Couplage fort : les classes peuvent cacher leurs dépendances en accédant à l'état global.
- État global : l'état partagé et mutable peut provoquer des bugs difficiles à trouver.
- Effets secondaires cachés : les méthodes qui comptent sur un Singleton ne font pas apparaître cette dépendance dans leur signature, ce qui complique le raisonnement et les tests.
Impact sur les tests et le couplage
Les Singletons compliquent les tests unitaires car ils introduisent un état global persistant. Les tests risquent de laisser fuiter de l'état entre les exécutions, et le mock d'un Singleton peut devenir maladroit. Les équipes modernes favorisent souvent l'injection de dépendances parce qu'elle rend les dépendances explicites et faciles à remplacer lors des tests.3
Trouver le bon équilibre
Lorsque vous décidez d'utiliser ou non un Singleton, pesez la commodité par rapport à la maintenabilité et à la testabilité à long terme. Pour les bases de code héritées, les refactorings incrémentaux vers l'injection de dépendances sont souvent la voie la plus sûre : conservez le comportement tout en réduisant le couplage caché et en améliorant la testabilité.
Les Singletons échangent la simplicité contre l'état global, alors choisissez avec discernement en fonction des besoins de votre équipe.
Points clés à retenir
- Utilisez les Singletons avec parcimonie et seulement pour les services qui doivent réellement être uniques.
- Préférez l'injection de dépendances explicite pour un meilleur découplage et une meilleure testabilité.
- Si vous devez utiliser un Singleton, rendez-le paresseux et soyez attentif à la concurrence et à la sécurité des threads.
- Pour les systèmes hérités, éliminez progressivement les Singletons en introduisant des interfaces et de l'ID au niveau de la racine de composition.
Comment implémenter le pattern Singleton en TypeScript

Passons de l'abstrait au pratique et construisons un Singleton moderne et typé en TypeScript. La recette secrète est un constructeur private et une méthode static qui joue le rôle de gardien. Cette combinaison garantit qu'aucune autre partie de votre application ne peut créer une nouvelle instance ; tout le monde passe par l'unique point d'entrée.2
Pour notre exemple pratique, nous allons créer un ConfigManager. Cette classe charge et fournit les paramètres de l'application, garantissant que chaque composant lit la même source de vérité.
Construire un ConfigManager typé et sûr
// Un exemple pratique du pattern Singleton pour la gestion de configuration.
class ConfigManager {
// 1. Une propriété statique privée pour contenir l'unique instance.
private static instance: ConfigManager;
// 2. Un endroit pour stocker nos données de configuration.
private settings: Map<string, any> = new Map();
// 3. Le constructeur privé. Cela empêche `new ConfigManager()` de fonctionner ailleurs.
private constructor() {
// Dans une vraie application, vous chargeriez depuis un fichier, des variables d'environnement ou un service.
console.log("Initialisation de l'instance ConfigManager...");
this.settings.set("API_URL", "https://api.example.com");
this.settings.set("TIMEOUT", 5000);
}
// 4. La méthode publique et statique qui contrôle l'accès à l'unique instance.
public static getInstance(): ConfigManager {
if (!ConfigManager.instance) {
ConfigManager.instance = new ConfigManager();
}
return ConfigManager.instance;
}
// 5. Une méthode publique classique pour obtenir un paramètre spécifique.
public get(key: string): any {
return this.settings.get(key);
}
}
// TypeScript vous empêchera de faire ceci :
// const config = new ConfigManager(); // Erreur : le constructeur de la classe 'ConfigManager' est privé.
Cette structure utilise les modificateurs d'accès de TypeScript pour imposer une classe à instance unique et une initialisation paresseuse. Le constructeur private et le pattern de l'instance static sont simples et efficaces pour de nombreux besoins simples.
Mettre le Singleton en œuvre dans un service
class ApiService {
private apiUrl: string;
constructor() {
const config = ConfigManager.getInstance();
this.apiUrl = config.get("API_URL");
console.log(`ApiService initialisé avec l'URL API : ${this.apiUrl}`);
}
public fetchData(): void {
console.log(`Récupération des données depuis ${this.apiUrl}...`);
// La logique réelle de récupération de données se trouverait ici.
}
}
// --- Point d'entrée de l'application ---
console.log("Démarrage de l'application...");
const service1 = new ApiService();
service1.fetchData();
const service2 = new ApiService();
console.log("Application terminée.");
Lorsque vous exécutez ce code, vous devriez voir le message d'initialisation de ConfigManager une seule fois, prouvant que les deux services ont reçu la même instance.
Pourquoi les Singletons ont si mauvaise réputation
Le pattern Singleton paraît séduisant parce qu'il est simple et vous donne un objet accessible globalement. Ce qui peut mal tourner, c'est le couplage caché, l'état global mutable et les cauchemars de tests. Lorsqu'une classe accède silencieusement à une instance globale, elle cache une dépendance qui devrait être explicite dans son constructeur. Cela rend le système plus difficile à comprendre et à tester.3
Cauchemar en concurrence et état global
Les Singletons avec état peuvent provoquer des conditions de course dans des scénarios concurrents. Prenez l'exemple d'un SessionCounter où deux requêtes simultanées incrémentent le même compteur. Sans synchronisation, les deux peuvent lire la même valeur de départ et écrire des mises à jour conflictuelles. Ces bugs dépendent du timing et sont difficiles à reproduire.
L'énigme des tests
Les Singletons rendent les tests unitaires fragiles parce que l'état peut fuir entre les tests et le mock devient difficile. Les tests peuvent commencer à dépendre de l'ordre d'exécution, et la suite devient instable. C'est pourquoi les équipes adoptent souvent l'injection de dépendances : elle rend les dépendances explicites et faciles à simuler.
Malgré ces problèmes, les Singletons persistent dans la nature. Ils se propagent souvent dans une base de code une fois introduits, ce qui explique pourquoi l'audit et le refactoring incrémental sont importants lors de l'amélioration de l'architecture.
Alternatives modernes au pattern Singleton
Après avoir vu les risques introduits par les Singletons, vous vous demandez probablement : « Que devrais-je utiliser à la place ? » L'injection de dépendances (DI) est l'approche de référence pour gérer les ressources partagées. La DI rend les dépendances explicites et améliore la testabilité et la modularité.
Injection de dépendances vs Singletons
Comparez l'original ApiService, qui va chercher un Singleton, avec une version qui accepte un gestionnaire de configuration via son constructeur.
interface IConfigManager {
get(key: string): any;
}
class ApiService {
private apiUrl: string;
constructor(config: IConfigManager) {
this.apiUrl = config.get("API_URL");
}
}
Désormais ApiService dépend uniquement du contrat IConfigManager. Pendant les tests, vous pouvez fournir un faux ou un mock, rendant les tests rapides et prévisibles.
En inversant le contrôle de qui crée les dépendances, les composants deviennent plus ciblés et flexibles. Cette idée est au cœur du principe d'inversion des dépendances.
Le rôle des conteneurs IoC
Un conteneur d'Inversion de Contrôle (IoC) gère la création et l'injection des objets pour votre application. Les frameworks TypeScript populaires fournissent des conteneurs DI intégrés, comme NestJS et Angular, ou des bibliothèques comme InversifyJS pour des projets généraux.45
Les conteneurs vous permettent de choisir comment les objets sont partagés : cycles de vie transitoires, scoped ou de type singleton. Cela vous donne les avantages d'une instance partagée unique sans le couplage caché d'un Singleton programmatique.
Comment refactorer des Singletons dans une base de code héritée
Travaillez de manière incrémentale. Identifiez où le Singleton est utilisé, définissez une interface claire qui décrit son comportement, et commencez à changer les consommateurs individuels pour accepter l'interface via l'injection par constructeur. Ensuite, branchez l'instance concrète à la racine de composition ou laissez un conteneur DI la gérer.
Étape 1 : Identifier et isoler le Singleton
Trouvez chaque appel à MySingleton.getInstance() et tracez une frontière autour des responsabilités du Singleton. Définissez une interface qui liste les méthodes publiques nécessaires.
Étape 2 : Introduire l'injection de dépendances de manière incrémentale
Refactorez un consommateur à la fois :
- Changez le constructeur pour accepter l'interface.
- Remplacez les appels directs à
getInstance()par des appels à l'instance injectée. - Au point d'instanciation, passez l'instance Singleton jusqu'à ce que la migration complète soit terminée.
Cela permet de garder l'application stable tout en réduisant le couplage caché.
Étape 3 : Remplacer le Singleton par une instance gérée
Une fois que les consommateurs prennent leurs dépendances via l'interface, vous pouvez supprimer le getInstance() statique et faire de l'implémentation une classe ordinaire avec un constructeur public. Créez une instance unique à la racine de composition et passez-la aux endroits nécessaires, ou laissez un conteneur DI gérer le cycle de vie et la portée.
Réponses à vos questions brûlantes sur les Singletons
Les Singletons sont-ils toujours une mauvaise idée ?
Pas toujours. Ils peuvent avoir du sens pour des services réellement uniques et sans état, comme un logger centralisé ou un adaptateur matériel. Même dans ce cas, la DI et une racine de composition contrôlée offrent souvent le même comportement avec une meilleure testabilité.
Comment les Singletons sabotent-ils les tests unitaires ?
Ils introduisent un état global persistant qui peut fuir entre les tests et rendre le mock difficile. Les tests peuvent devenir dépendants de l'ordre et instables. La DI simplifie les tests car les mocks peuvent être injectés directement.
Une classe statique n'est-elle pas essentiellement la même chose ?
Non. Une classe statique ne contient que des membres statiques et ne peut pas être instanciée. Un Singleton a une vraie instance et peut implémenter des interfaces et être passé comme objet. Les deux approches peuvent conduire à un couplage fort, donc préférez la DI pour plus de flexibilité.
Prochaines étapes pour votre équipe
Commencez une discussion sur les endroits où, le cas échéant, une instance partagée unique est réellement requise. Prototyperez la DI dans un module isolé, adoptez des standards de codage clairs et utilisez des outils pour mesurer la dette technique. Le pairing et le feedback continu aident à sécuriser et accélérer les refactorings.
Rappelez-vous, aucun pattern de conception n'est une solution miracle. Les Singletons ont leur place, mais ils doivent être utilisés avec discernement et accompagnés d'interfaces propres et de règles de responsabilité claires.
FAQ — Questions & Réponses rapides
Q : Quand un Singleton est-il approprié ? A : Quand une ressource est vraiment unique et sans état, comme un logger centralisé ou un adaptateur matériel. Préférez la DI lorsque c'est possible.
Q : Comment puis-je tester du code qui utilise un Singleton aujourd'hui ? A : Introduisez une interface, refactorez les consommateurs pour accepter l'interface et injectez un double de test. Faites cela de manière incrémentale pour éviter des régressions à grande échelle.
Q : Quel est le chemin le plus sûr pour supprimer des Singletons d'une application héritée ? A : Cartographiez les usages, définissez des interfaces, refactorez les consommateurs pour accepter des dépendances, puis créez et injectez une instance unique à la racine de composition ou utilisez un conteneur DI.
L’IA écrit du code.Vous le faites durer.
À l’ère de l’accélération de l’IA, le code propre n’est pas seulement une bonne pratique — c’est la différence entre les systèmes qui évoluent et les codebases qui s’effondrent sous leur propre poids.