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.

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