Menu
Coddy logo textTech

Patron Singleton

Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 91 sur 104.

Le pattern Singleton garantit qu'une classe n'a qu'une seule instance dans tout le programme et fournit un point d'accès global à celle-ci. Ceci est utile pour les ressources qui doivent être partagées, comme un gestionnaire de configuration, un journal (logger) ou un pool de connexions à une base de données.

L'idée clé est de rendre le constructeur privé afin que personne ne puisse créer d'instances directement, puis de fournir une méthode statique qui renvoie l'instance unique :

#include <iostream>
#include <string>

class Logger {
private:
    std::string logFile;
    
    // Constructeur privé - empêche l'instanciation directe
    Logger() : logFile("app.log") {}
    
    // Supprimer le constructeur de copie et l'opérateur d'affectation
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

public:
    // Méthode statique pour accéder à l'instance unique
    static Logger& getInstance() {
        static Logger instance;  // Créée une seule fois, vit jusqu'à la fin du programme
        return instance;
    }
    
    void log(const std::string& message) {
        std::cout << "[LOG] " << message << "\n";
    }
};

int main() {
    Logger::getInstance().log("Application started");
    
    Logger& logger = Logger::getInstance();
    logger.log("Processing data");
}

L'élément static Logger instance à l'intérieur de getInstance() est une fonctionnalité de C++11 appelée « statiques magiques » (magic statics) — elle est thread-safe et garantie d'être initialisée une seule fois. En supprimant le constructeur de copie et l'opérateur d'affectation, nous empêchons quiconque de créer des copies de notre singleton.

Utilisez le Singleton avec parcimonie - il introduit un état global qui peut rendre les tests plus difficiles. Il est mieux adapté aux ressources véritablement partagées où le fait d'avoir plusieurs instances causerait des problèmes.

challenge icon

Défi

Facile

Construisons un singleton GameSettings qui gère la configuration d'une application de jeu. C'est un cas d'utilisation parfait pour le motif Singleton — vous voulez exactement un seul gestionnaire de paramètres auquel toutes les parties de votre jeu peuvent accéder, garantissant une configuration cohérente partout.

Vous organiserez votre code sur trois fichiers :

  • GameSettings.h : Définissez votre classe singleton qui gère la configuration du jeu.

    Votre classe GameSettings doit stocker trois paramètres en tant que membres privés : difficulty (chaîne de caractères), volume (entier de 0 à 100) et fullscreen (booléen). Initialisez-les avec des valeurs par défaut : "Normal", 50 et false.

    Implémentez correctement le motif Singleton :

    • Rendez le constructeur privé
    • Supprimez le constructeur de copie et l'opérateur d'affectation
    • Fournissez une méthode statique getInstance() qui retourne une référence vers l'instance unique

    Ajoutez ces méthodes publiques pour interagir avec les paramètres :

    • setDifficulty(const std::string& diff) — met à jour la difficulté
    • setVolume(int vol) — met à jour le volume
    • setFullscreen(bool fs) — met à jour le mode plein écran
    • displaySettings() — affiche tous les paramètres actuels
  • GameModule.h : Créez un module séparé qui accède au singleton.

    Définissez une fonction nommée applyGamePreset qui prend un nom de préréglage (chaîne de caractères). Cette fonction doit accéder au singleton GameSettings et le configurer en fonction du préréglage :

    • "casual" — définit la difficulté sur "Easy", le volume sur 70, le plein écran sur false
    • "competitive" — définit la difficulté sur "Hard", le volume sur 30, le plein écran sur true
    • Tout autre préréglage — conserve les paramètres par défaut inchangés

    Cela démontre comment différentes parties d'un programme peuvent accéder et modifier la même instance de singleton.

  • main.cpp : Rassemblez tout pour montrer le singleton en action.

    Lisez deux entrées :

    1. Un nom de préréglage (chaîne de caractères)
    2. Un niveau de volume personnalisé (entier)

    Tout d'abord, affichez les paramètres par défaut initiaux en appelant displaySettings() sur le singleton. Affichez une ligne vide après.

    Ensuite, appliquez le préréglage en utilisant votre fonction applyGamePreset et affichez à nouveau les paramètres. Affichez une ligne vide après.

    Enfin, remplacez uniquement le volume par la valeur d'entrée personnalisée et affichez les paramètres finaux.

La méthode displaySettings() doit produire une sortie dans ce format exact :

Difficulty: [value]
Volume: [value]
Fullscreen: [yes/no]

Par exemple, avec les entrées casual et 85 :

Difficulty: Normal
Volume: 50
Fullscreen: no

Difficulty: Easy
Volume: 70
Fullscreen: no

Difficulty: Easy
Volume: 85
Fullscreen: no

Avec les entrées competitive et 20 :

Difficulty: Normal
Volume: 50
Fullscreen: no

Difficulty: Hard
Volume: 30
Fullscreen: yes

Difficulty: Hard
Volume: 20
Fullscreen: yes

Remarquez comment main.cpp et GameModule.h accèdent tous deux à la même instance de singleton — les modifications apportées à un endroit sont visibles partout. C'est la puissance du motif Singleton pour les ressources partagées comme les paramètres de jeu.

Aide-mémoire

Le pattern Singleton garantit qu'une classe n'a qu'une seule instance dans tout le programme et fournit un accès global à celle-ci. Il est utile pour les ressources partagées comme les gestionnaires de configuration, les journaux (loggers) ou les pools de connexions de base de données.

Étapes clés de l'implémentation :

  • Rendre le constructeur privé pour empêcher l'instanciation directe
  • Supprimer le constructeur de copie et l'opérateur d'affectation pour empêcher les copies
  • Fournir une méthode statique qui renvoie une référence à l'instance unique
class Logger {
private:
    std::string logFile;
    
    // Constructeur privé
    Logger() : logFile("app.log") {}
    
    // Supprimer le constructeur de copie et l'opérateur d'affectation
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

public:
    // Méthode statique pour accéder à l'instance unique
    static Logger& getInstance() {
        static Logger instance;  // Créée une seule fois, vit jusqu'à la fin du programme
        return instance;
    }
    
    void log(const std::string& message) {
        std::cout << "[LOG] " << message << "\n";
    }
};

// Utilisation
Logger::getInstance().log("Application started");

Logger& logger = Logger::getInstance();
logger.log("Processing data");

Le static Logger instance à l'intérieur de getInstance() utilise les "magic statics" du C++11 — c'est thread-safe et garanti d'être initialisé une seule fois.

Utilisez le Singleton avec parcimonie car il introduit un état global qui peut rendre les tests plus difficiles. Il est mieux adapté aux ressources véritablement partagées où plusieurs instances causeraient des problèmes.

Essayez vous-même

#include <iostream>
#include <string>
#include "GameSettings.h"
#include "GameModule.h"

using namespace std;

int main() {
    // Lire les entrées
    string presetName;
    int customVolume;
    cin >> presetName;
    cin >> customVolume;
    
    // TODO: Obtenir l'instance singleton de GameSettings
    
    // TODO: Afficher les paramètres par défaut initiaux
    
    // TODO: Imprimer une ligne vide
    
    // TODO: Appliquer le préréglage en utilisant la fonction applyGamePreset
    
    // TODO: Afficher les paramètres après avoir appliqué le préréglage
    
    // TODO: Imprimer une ligne vide
    
    // TODO: Remplacer le volume par customVolume
    
    // TODO: Afficher les paramètres finaux
    
    return 0;
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation Orientée Objet