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.
Sfida
FacileCreiamo 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
GameSettingsdovrebbe memorizzare tre impostazioni come membri privati:difficulty(stringa),volume(intero da 0 a 100) efullscreen(booleano). Inizializzale con i valori predefiniti:"Normal",50efalse.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 volumesetFullscreen(bool fs): aggiorna la modalità a schermo interodisplaySettings(): stampa tutte le impostazioni correnti
GameModule.h: crea un modulo separato che accede al singleton.Definisci una funzione chiamata
applyGamePresetche accetta il nome di un preset (stringa). Questa funzione dovrebbe accedere al singletonGameSettingse configurarlo in base al preset:"casual": imposta la difficoltà su"Easy", il volume su70e lo schermo intero sufalse"competitive": imposta la difficoltà su"Hard", il volume su30e lo schermo intero sutrue- 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:
- Il nome di un preset (stringa)
- 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
applyGamePresete 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: noCon gli input competitive e 20:
Difficulty: Normal
Volume: 50
Fullscreen: no
Difficulty: Hard
Volume: 30
Fullscreen: yes
Difficulty: Hard
Volume: 20
Fullscreen: yesNota 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;
}
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
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti10Panoramica della STL
Panoramica e filosofia della STLContenitori STLIteratoriAlgoritmi STLFuntori ed espressioni lambdaRiepilogo - Frequenza delle parole13Design Pattern Parte 1
Introduzione ai Design PatternPattern SingletonFactory e Abstract FactoryPattern BuilderPattern ObserverPattern Strategy2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore genericoEsercitati da solo: Compilatore C++ online