Menu
Coddy logo textTech

Singleton-Muster

Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 91 von 104.

Das Singleton-Muster stellt sicher, dass eine Klasse während des gesamten Programms nur eine einzige Instanz hat, und bietet einen globalen Zugriffspunkt darauf. Dies ist nützlich für Ressourcen, die gemeinsam genutzt werden sollten, wie ein Konfigurationsmanager, Logger oder ein Datenbank-Verbindungspool.

Die Kernidee besteht darin, den Konstruktor privat zu machen, damit niemand Instanzen direkt erstellen kann, und dann eine statische Methode bereitzustellen, die die einzige Instanz zurückgibt:

#include <iostream>
#include <string>

class Logger {
private:
    std::string logFile;
    
    // Privater Konstruktor - verhindert direkte Instanziierung
    Logger() : logFile("app.log") {}
    
    // Kopierkonstruktor und Zuweisungsoperator löschen
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

public:
    // Statische Methode für den Zugriff auf die einzige Instanz
    static Logger& getInstance() {
        static Logger instance;  // Einmal erstellt, bleibt bis zum Programmende bestehen
        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");
}

Die static Logger instance innerhalb von getInstance() ist ein C++11-Feature namens „Magic Statics“ – es ist threadsicher und wird garantiert nur einmal initialisiert. Durch das Löschen des Kopierkonstruktors und des Zuweisungsoperators verhindern wir, dass Kopien unseres Singletons erstellt werden.

Verwenden Sie Singletons sparsam – sie führen einen globalen Zustand ein, der das Testen erschweren kann. Sie eignen sich am besten für wirklich gemeinsam genutzte Ressourcen, bei denen mehrere Instanzen Probleme verursachen würden.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein GameSettings-Singleton erstellen, das die Konfiguration für eine Spielanwendung verwaltet. Dies ist ein perfekter Anwendungsfall für das Singleton-Muster – Sie möchten genau einen Einstellungsmanager, auf den alle Teile Ihres Spiels zugreifen können, um eine konsistente Konfiguration im gesamten Programm zu gewährleisten.

Sie werden Ihren Code auf drei Dateien verteilen:

  • GameSettings.h: Definieren Sie Ihre Singleton-Klasse, die die Spielkonfiguration verwaltet.

    Ihre GameSettings-Klasse sollte drei Einstellungen als private Member speichern: difficulty (String), volume (Integer von 0-100) und fullscreen (Boolean). Initialisieren Sie diese mit Standardwerten: "Normal", 50 und false.

    Implementieren Sie das Singleton-Muster ordnungsgemäß:

    • Machen Sie den Konstruktor privat
    • Löschen Sie den Kopierkonstruktor und den Zuweisungsoperator
    • Stellen Sie eine statische getInstance()-Methode bereit, die eine Referenz auf die einzige Instanz zurückgibt

    Fügen Sie diese öffentlichen Methoden hinzu, um mit den Einstellungen zu interagieren:

    • setDifficulty(const std::string& diff) — aktualisiert die Schwierigkeit
    • setVolume(int vol) — aktualisiert die Lautstärke
    • setFullscreen(bool fs) — aktualisiert den Vollbildmodus
    • displaySettings() — gibt alle aktuellen Einstellungen aus
  • GameModule.h: Erstellen Sie ein separates Modul, das auf das Singleton zugreift.

    Definieren Sie eine Funktion namens applyGamePreset, die einen Preset-Namen (String) entgegennimmt. Diese Funktion sollte auf das GameSettings-Singleton zugreifen und es basierend auf dem Preset konfigurieren:

    • "casual" — setzt die Schwierigkeit auf "Easy", die Lautstärke auf 70, Vollbild auf false
    • "competitive" — setzt die Schwierigkeit auf "Hard", die Lautstärke auf 30, Vollbild auf true
    • Jedes andere Preset — lässt die Standardeinstellungen unverändert

    Dies zeigt, wie verschiedene Teile eines Programms auf dieselbe Singleton-Instanz zugreifen und diese ändern können.

  • main.cpp: Bringen Sie alles zusammen, um das Singleton in Aktion zu zeigen.

    Lesen Sie zwei Eingaben ein:

    1. Einen Preset-Namen (String)
    2. Einen benutzerdefinierten Lautstärkepegel (Integer)

    Zeigen Sie zuerst die anfänglichen Standardeinstellungen an, indem Sie displaySettings() auf dem Singleton aufrufen. Geben Sie danach eine Leerzeile aus.

    Wenden Sie dann das Preset mit Ihrer applyGamePreset-Funktion an und zeigen Sie die Einstellungen erneut an. Geben Sie danach eine Leerzeile aus.

    Überschreiben Sie schließlich nur die Lautstärke mit dem benutzerdefinierten Eingabewert und zeigen Sie die endgültigen Einstellungen an.

Die Methode displaySettings() sollte die Ausgabe in genau diesem Format erzeugen:

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

Zum Beispiel bei den Eingaben casual und 85:

Difficulty: Normal
Volume: 50
Fullscreen: no

Difficulty: Easy
Volume: 70
Fullscreen: no

Difficulty: Easy
Volume: 85
Fullscreen: no

Bei den Eingaben competitive und 20:

Difficulty: Normal
Volume: 50
Fullscreen: no

Difficulty: Hard
Volume: 30
Fullscreen: yes

Difficulty: Hard
Volume: 20
Fullscreen: yes

Beachten Sie, wie sowohl main.cpp als auch GameModule.h auf dieselbe Singleton-Instanz zugreifen – Änderungen, die an einer Stelle vorgenommen werden, sind überall sichtbar. Dies ist die Stärke des Singleton-Musters für gemeinsam genutzte Ressourcen wie Spieleinstellungen.

Spickzettel

Das Singleton-Muster stellt sicher, dass eine Klasse während des gesamten Programms nur eine Instanz hat, und bietet einen globalen Zugriff darauf. Es ist nützlich für gemeinsam genutzte Ressourcen wie Konfigurationsmanager, Logger oder Datenbank-Verbindungspools.

Wichtige Implementierungsschritte:

  • Machen Sie den Konstruktor privat, um eine direkte Instanziierung zu verhindern
  • Löschen Sie den Kopierkonstruktor und den Zuweisungsoperator, um Kopien zu verhindern
  • Stellen Sie eine statische Methode bereit, die eine Referenz auf die einzige Instanz zurückgibt
class Logger {
private:
    std::string logFile;
    
    // Privater Konstruktor
    Logger() : logFile("app.log") {}
    
    // Kopierkonstruktor und Zuweisungsoperator löschen
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

public:
    // Statische Methode für den Zugriff auf die einzige Instanz
    static Logger& getInstance() {
        static Logger instance;  // Wird einmal erstellt, existiert bis zum Programmende
        return instance;
    }
    
    void log(const std::string& message) {
        std::cout << "[LOG] " << message << "\n";
    }
};

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

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

Das static Logger instance innerhalb von getInstance() verwendet C++11 „Magic Statics“ – es ist threadsicher und wird garantiert nur einmal initialisiert.

Verwenden Sie das Singleton sparsam, da es einen globalen Zustand einführt, der das Testen erschweren kann. Am besten geeignet für wirklich gemeinsam genutzte Ressourcen, bei denen mehrere Instanzen Probleme verursachen würden.

Probier es selbst

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

using namespace std;

int main() {
    // Eingaben lesen
    string presetName;
    int customVolume;
    cin >> presetName;
    cin >> customVolume;
    
    // TODO: Die GameSettings Singleton-Instanz abrufen
    
    // TODO: Anfängliche Standardeinstellungen anzeigen
    
    // TODO: Eine Leerzeile ausgeben
    
    // TODO: Das Preset mit der Funktion applyGamePreset anwenden
    
    // TODO: Einstellungen nach dem Anwenden des Presets anzeigen
    
    // TODO: Eine Leerzeile ausgeben
    
    // TODO: Die Lautstärke mit customVolume überschreiben
    
    // TODO: Finale Einstellungen anzeigen
    
    return 0;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung