Wzorzec Singleton
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 91 z 104.
Wzorzec Singleton gwarantuje, że klasa ma tylko jedną instancję w całym programie, i zapewnia globalny punkt dostępu do niej. Jest to przydatne w przypadku zasobów, które powinny być współdzielone, takich jak menedżer konfiguracji, rejestrator lub pula połączeń z bazą danych.
Kluczową ideą jest ustawienie konstruktora jako prywatnego, aby nikt nie mógł bezpośrednio tworzyć instancji, a następnie udostępnienie metody statycznej, która zwraca jedyną instancję:
#include <iostream>
#include <string>
class Logger {
private:
std::string logFile;
// Prywatny konstruktor – uniemożliwia bezpośrednie tworzenie instancji
Logger() : logFile("app.log") {}
// Usuń konstruktor kopiujący i operator przypisania
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
public:
// Metoda statyczna zapewniająca dostęp do jedynej instancji
static Logger& getInstance() {
static Logger instance; // Tworzona tylko raz, istnieje do końca działania programu
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");
}static Logger instance wewnątrz getInstance() to funkcja języka C++11 zwana „magic statics” — jest bezpieczna wątkowo i gwarantuje, że zostanie zainicjalizowana tylko raz. Usuwając konstruktor kopiujący i operator przypisania, uniemożliwiamy tworzenie kopii naszego singletona.
Używaj wzorca Singleton oszczędnie — wprowadza stan globalny, który może utrudnić testowanie. Najlepiej sprawdza się w przypadku rzeczywiście współdzielonych zasobów, dla których wiele instancji mogłoby powodować problemy.
Wyzwanie
ŁatwyZbudujmy singleton GameSettings, który zarządza konfiguracją aplikacji gry. To idealny przypadek użycia wzorca Singleton. Chcesz mieć dokładnie jednego menedżera ustawień, do którego mają dostęp wszystkie części gry, co zapewnia spójną konfigurację w całej aplikacji.
Rozmieścisz kod w trzech plikach:
GameSettings.h: Zdefiniuj klasę singletona, która zarządza konfiguracją gry.Klasa
GameSettingspowinna przechowywać trzy ustawienia jako prywatne składowe:difficulty(ciąg znaków),volume(liczba całkowita z zakresu 0-100) orazfullscreen(wartość logiczna). Zainicjuj je wartościami domyślnymi:"Normal",50ifalse.Zaimplementuj poprawnie wzorzec Singleton:
- Ustaw konstruktor jako prywatny
- Usuń konstruktor kopiujący i operator przypisania
- Udostępnij statyczną metodę
getInstance(), która zwraca referencję do jedynej instancji
Dodaj następujące publiczne metody do obsługi ustawień:
setDifficulty(const std::string& diff): aktualizuje poziom trudnościsetVolume(int vol): aktualizuje głośnośćsetFullscreen(bool fs): aktualizuje tryb pełnoekranowydisplaySettings(): wyświetla wszystkie bieżące ustawienia
GameModule.h: Utwórz osobny moduł, który korzysta z singletona.Zdefiniuj funkcję o nazwie
applyGamePreset, która przyjmuje nazwę ustawienia wstępnego (ciąg znaków). Ta funkcja powinna uzyskać dostęp do singletonaGameSettingsi skonfigurować go na podstawie ustawienia wstępnego:"casual": ustawia poziom trudności na"Easy", głośność na70, a tryb pełnoekranowy nafalse"competitive": ustawia poziom trudności na"Hard", głośność na30, a tryb pełnoekranowy natrue- Każde inne ustawienie wstępne: pozostawia ustawienia domyślne bez zmian
To pokazuje, jak różne części programu mogą uzyskiwać dostęp do tej samej instancji singletona i ją modyfikować.
main.cpp: Połącz wszystkie elementy, aby pokazać singletona w działaniu.Wczytaj dwa dane wejściowe:
- Nazwę ustawienia wstępnego (ciąg znaków)
- Niestandardowy poziom głośności (liczba całkowita)
Najpierw wyświetl początkowe ustawienia domyślne, wywołując metodę
displaySettings()singletona. Następnie wypisz pusty wiersz.Potem zastosuj ustawienie wstępne za pomocą funkcji
applyGamePreseti ponownie wyświetl ustawienia. Następnie wypisz pusty wiersz.Na koniec zastąp tylko wartość głośności wartością wczytaną z danych wejściowych i wyświetl ustawienia końcowe.
Metoda displaySettings() powinna wyświetlać dane dokładnie w tym formacie:
Difficulty: [value]
Volume: [value]
Fullscreen: [yes/no]Na przykład dla danych wejściowych casual i 85:
Difficulty: Normal
Volume: 50
Fullscreen: no
Difficulty: Easy
Volume: 70
Fullscreen: no
Difficulty: Easy
Volume: 85
Fullscreen: noDla danych wejściowych competitive i 20:
Difficulty: Normal
Volume: 50
Fullscreen: no
Difficulty: Hard
Volume: 30
Fullscreen: yes
Difficulty: Hard
Volume: 20
Fullscreen: yesZwróć uwagę, że zarówno main.cpp, jak i GameModule.h uzyskują dostęp do tej samej instancji singletona: zmiany wprowadzone w jednym miejscu są widoczne wszędzie. Na tym polega siła wzorca Singleton w przypadku współdzielonych zasobów, takich jak ustawienia gry.
Spróbuj swoich sił
#include <iostream>
#include <string>
#include "GameSettings.h"
#include "GameModule.h"
using namespace std;
int main() {
// Odczytaj dane wejściowe
string presetName;
int customVolume;
cin >> presetName;
cin >> customVolume;
// TODO: Pobierz instancję singletonu GameSettings
// TODO: Wyświetl początkowe ustawienia domyślne
// TODO: Wypisz pusty wiersz
// TODO: Zastosuj ustawienia wstępne za pomocą funkcji applyGamePreset
// TODO: Wyświetl ustawienia po zastosowaniu ustawień wstępnych
// TODO: Wypisz pusty wiersz
// TODO: Zastąp poziom głośności wartością customVolume
// TODO: Wyświetl ustawienia końcowe
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników10Przegląd STL
Przegląd i filozofia STLKontenery STLIteratoryAlgorytmy STLFunktory i wyrażenia lambdaPowtórzenie – częstość występowania słów13Wzorce projektowe, część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonFabryka i fabryka abstrakcyjnaWzorzec BuilderWzorzec ObserverWzorzec Strategy2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online