Menu
Coddy logo textTech

Patrón Singleton

Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 91 de 104.

El patrón Singleton garantiza que una clase tenga una única instancia en todo el programa y proporciona un punto de acceso global a ella. Esto es útil para recursos que deben compartirse, como un gestor de configuración, un registrador (logger) o un pool de conexiones a la base de datos.

La idea clave es hacer que el constructor sea privado para que nadie pueda crear instancias directamente, y luego proporcionar un método estático que devuelva la instancia única:

#include <iostream>
#include <string>

class Logger {
private:
    std::string logFile;
    
    // Constructor privado - evita la instanciación directa
    Logger() : logFile("app.log") {}
    
    // Eliminar el constructor de copia y el operador de asignación
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

public:
    // Método estático para acceder a la instancia única
    static Logger& getInstance() {
        static Logger instance;  // Creado una vez, vive hasta que termina el programa
        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");
}

La static Logger instance dentro de getInstance() es una característica de C++11 llamada "magic statics": es segura para hilos (thread-safe) y se garantiza que se inicialice solo una vez. Al eliminar el constructor de copia y el operador de asignación, evitamos que alguien cree copias de nuestro singleton.

Utiliza Singleton con moderación: introduce un estado global que puede dificultar las pruebas. Es más adecuado para recursos verdaderamente compartidos donde tener múltiples instancias causaría problemas.

challenge icon

Desafío

Fácil

Vamos a construir un singleton GameSettings que gestione la configuración de una aplicación de juego. Este es un caso de uso perfecto para el patrón Singleton: quieres exactamente un gestor de configuraciones al que todas las partes de tu juego puedan acceder, garantizando una configuración consistente en todo momento.

Organizarás tu código en tres archivos:

  • GameSettings.h: Define tu clase singleton que gestiona la configuración del juego.

    Tu clase GameSettings debe almacenar tres configuraciones como miembros privados: difficulty (string), volume (entero de 0-100) y fullscreen (booleano). Inicialízalos con valores por defecto: "Normal", 50 y false.

    Implementa el patrón Singleton correctamente:

    • Haz que el constructor sea privado
    • Elimina el constructor de copia y el operador de asignación
    • Proporciona un método estático getInstance() que devuelva una referencia a la instancia única

    Añade estos métodos públicos para interactuar con la configuración:

    • setDifficulty(const std::string& diff) — actualiza la dificultad
    • setVolume(int vol) — actualiza el volumen
    • setFullscreen(bool fs) — actualiza el modo de pantalla completa
    • displaySettings() — imprime todas las configuraciones actuales
  • GameModule.h: Crea un módulo separado que acceda al singleton.

    Define una función llamada applyGamePreset que reciba un nombre de preajuste (string). Esta función debe acceder al singleton GameSettings y configurarlo basándose en el preajuste:

    • "casual" — establece la dificultad a "Easy", el volumen a 70, y pantalla completa a false
    • "competitive" — establece la dificultad a "Hard", el volumen a 30, y pantalla completa a true
    • Cualquier otro preajuste — mantiene la configuración por defecto sin cambios

    Esto demuestra cómo diferentes partes de un programa pueden acceder y modificar la misma instancia singleton.

  • main.cpp: Une todo para mostrar el singleton en acción.

    Lee dos entradas:

    1. Un nombre de preajuste (string)
    2. Un nivel de volumen personalizado (entero)

    Primero, muestra la configuración inicial por defecto llamando a displaySettings() en el singleton. Imprime una línea en blanco después.

    Luego aplica el preajuste usando tu función applyGamePreset y muestra la configuración de nuevo. Imprime una línea en blanco después.

    Finalmente, sobrescribe solo el volumen con el valor de entrada personalizado y muestra la configuración final.

El método displaySettings() debe mostrar la salida en este formato exacto:

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

Por ejemplo, con las entradas casual y 85:

Difficulty: Normal
Volume: 50
Fullscreen: no

Difficulty: Easy
Volume: 70
Fullscreen: no

Difficulty: Easy
Volume: 85
Fullscreen: no

Con las entradas competitive y 20:

Difficulty: Normal
Volume: 50
Fullscreen: no

Difficulty: Hard
Volume: 30
Fullscreen: yes

Difficulty: Hard
Volume: 20
Fullscreen: yes

Observa cómo tanto main.cpp como GameModule.h acceden a la misma instancia singleton; los cambios realizados en un lugar son visibles en todas partes. Este es el poder del patrón Singleton para recursos compartidos como la configuración de un juego.

Hoja de referencia

El patrón Singleton asegura que una clase tenga solo una instancia en todo el programa y proporciona acceso global a ella. Es útil para recursos compartidos como gestores de configuración, registradores (loggers) o pools de conexiones a bases de datos.

Pasos clave de implementación:

  • Hacer el constructor privado para evitar la instanciación directa
  • Eliminar el constructor de copia y el operador de asignación para evitar copias
  • Proporcionar un método estático que devuelva una referencia a la instancia única
class Logger {
private:
    std::string logFile;
    
    // Constructor privado
    Logger() : logFile("app.log") {}
    
    // Eliminar el constructor de copia y el operador de asignación
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

public:
    // Método estático para acceder a la instancia única
    static Logger& getInstance() {
        static Logger instance;  // Creada una vez, vive hasta que termina el programa
        return instance;
    }
    
    void log(const std::string& message) {
        std::cout << "[LOG] " << message << "\n";
    }
};

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

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

El static Logger instance dentro de getInstance() utiliza los "magic statics" de C++11; es seguro para hilos (thread-safe) y se garantiza que se inicialice solo una vez.

Use el Singleton con moderación, ya que introduce un estado global que puede dificultar las pruebas. Es más adecuado para recursos verdaderamente compartidos donde múltiples instancias causarían problemas.

Pruébalo tú mismo

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

using namespace std;

int main() {
    // Leer las entradas
    string presetName;
    int customVolume;
    cin >> presetName;
    cin >> customVolume;
    
    // TODO: Obtener la instancia singleton de GameSettings
    
    // TODO: Mostrar la configuración inicial predeterminada
    
    // TODO: Imprimir una línea en blanco
    
    // TODO: Aplicar el ajuste preestablecido usando la función applyGamePreset
    
    // TODO: Mostrar la configuración después de aplicar el ajuste preestablecido
    
    // TODO: Imprimir una línea en blanco
    
    // TODO: Sobrescribir el volumen con customVolume
    
    // TODO: Mostrar la configuración final
    
    return 0;
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación Orientada a Objetos