Menu
Coddy logo textTech

Pattern Singleton

Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 91 di 104.

Il pattern Singleton garantisce che una classe abbia una sola istanza in tutto il programma e fornisce un punto di accesso globale a essa. È utile per risorse da condividere, come un gestore della configurazione, un logger o un pool di connessioni al database.

L'idea chiave è rendere privato il costruttore, così che nessuno possa creare direttamente istanze, e poi fornire un metodo statico che restituisca l'unica istanza:

#include <iostream>
#include <string>

class Logger {
private:
    std::string logFile;
    
    // Costruttore privato - impedisce l'istanziazione diretta
    Logger() : logFile("app.log") {}
    
    // Elimina il costruttore di copia e l'operatore di assegnazione
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

public:
    // Metodo statico per accedere all'unica istanza
    static Logger& getInstance() {
        static Logger instance;  // Creata una sola volta, esiste fino al termine del programma
        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");
}

Il static Logger instance all'interno di getInstance() è una funzionalità di C++11 chiamata «magic statics»: è thread-safe e viene garantito che sia inizializzata una sola volta. Eliminando il costruttore di copia e l'operatore di assegnazione, impediamo a chiunque di creare copie del nostro singleton.

Usa Singleton con parsimonia: introduce uno stato globale che può rendere più difficili i test. È più adatto alle risorse effettivamente condivise, per le quali avere più istanze causerebbe problemi.

challenge icon

Sfida

Facile

Creiamo un singleton GameSettings che gestisce la configurazione di un'applicazione di gioco. Questo è un caso d'uso perfetto per il pattern Singleton. Vuoi un unico gestore delle impostazioni a cui possano accedere tutte le parti del gioco, garantendo una configurazione coerente ovunque.

Organizzerai il codice in tre file:

  • GameSettings.h: definisci la classe singleton che gestisce la configurazione del gioco.

    La classe GameSettings dovrebbe memorizzare tre impostazioni come membri privati: difficulty (stringa), volume (intero da 0 a 100) e fullscreen (booleano). Inizializzale con i valori predefiniti: "Normal", 50 e false.

    Implementa correttamente il pattern Singleton:

    • Rendi privato il costruttore
    • Elimina il costruttore di copia e l'operatore di assegnazione
    • Fornisci un metodo statico getInstance() che restituisca un riferimento all'unica istanza

    Aggiungi questi metodi pubblici per interagire con le impostazioni:

    • setDifficulty(const std::string& diff): aggiorna la difficoltà
    • setVolume(int vol): aggiorna il volume
    • setFullscreen(bool fs): aggiorna la modalità a schermo intero
    • displaySettings(): stampa tutte le impostazioni correnti
  • GameModule.h: crea un modulo separato che accede al singleton.

    Definisci una funzione chiamata applyGamePreset che accetta il nome di un preset (stringa). Questa funzione dovrebbe accedere al singleton GameSettings e configurarlo in base al preset:

    • "casual": imposta la difficoltà su "Easy", il volume su 70 e lo schermo intero su false
    • "competitive": imposta la difficoltà su "Hard", il volume su 30 e lo schermo intero su true
    • Qualsiasi altro preset: mantiene invariate le impostazioni predefinite

    Questo dimostra come diverse parti di un programma possano accedere alla stessa istanza singleton e modificarla.

  • main.cpp: assembla il tutto per mostrare il singleton in azione.

    Leggi due input:

    1. Il nome di un preset (stringa)
    2. Un livello di volume personalizzato (intero)

    Per prima cosa, visualizza le impostazioni predefinite iniziali chiamando displaySettings() sul singleton. Poi stampa una riga vuota.

    Quindi applica il preset usando la funzione applyGamePreset e visualizza di nuovo le impostazioni. Poi stampa una riga vuota.

    Infine, sostituisci solo il volume con il valore di input personalizzato e visualizza le impostazioni finali.

Il metodo displaySettings() dovrebbe produrre un output in questo formato esatto:

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

Per esempio, con gli input casual e 85:

Difficulty: Normal
Volume: 50
Fullscreen: no

Difficulty: Easy
Volume: 70
Fullscreen: no

Difficulty: Easy
Volume: 85
Fullscreen: no

Con gli input competitive e 20:

Difficulty: Normal
Volume: 50
Fullscreen: no

Difficulty: Hard
Volume: 30
Fullscreen: yes

Difficulty: Hard
Volume: 20
Fullscreen: yes

Nota come sia main.cpp sia GameModule.h accedano alla stessa istanza singleton: le modifiche apportate in un punto sono visibili ovunque. Questo è il potere del pattern Singleton per risorse condivise come le impostazioni di gioco.

Provalo tu

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

using namespace std;

int main() {
    // Leggi gli input
    string presetName;
    int customVolume;
    cin >> presetName;
    cin >> customVolume;
    
    // TODO: Ottieni l'istanza singleton di GameSettings
    
    // TODO: Mostra le impostazioni predefinite iniziali
    
    // TODO: Stampa una riga vuota
    
    // TODO: Applica il preset usando la funzione applyGamePreset
    
    // TODO: Mostra le impostazioni dopo aver applicato il preset
    
    // TODO: Stampa una riga vuota
    
    // TODO: Sostituisci il volume con customVolume
    
    // TODO: Mostra le impostazioni finali
    
    return 0;
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore C++ online