Паттерн Singleton
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 91 из 104.
Паттерн Singleton (Одиночка) гарантирует, что у класса есть только один экземпляр на протяжении всей работы программы, и предоставляет к нему глобальную точку доступа. Это полезно для ресурсов, которые должны быть общими, таких как менеджер конфигурации, логгер или пул соединений с базой данных.
Основная идея заключается в том, чтобы сделать конструктор приватным, чтобы никто не мог создавать экземпляры напрямую, а затем предоставить статический метод, который возвращает единственный экземпляр:
#include <iostream>
#include <string>
class Logger {
private:
std::string logFile;
// Private constructor - prevents direct instantiation
// Приватный конструктор — предотвращает прямое создание экземпляра
Logger() : logFile("app.log") {}
// Delete copy constructor and assignment operator
// Удаляем конструктор копирования и оператор присваивания
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
public:
// Static method to access the single instance
// Статический метод для доступа к единственному экземпляру
static Logger& getInstance() {
static Logger instance; // Created once, lives until program ends
// Создается один раз, живет до завершения программы
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 внутри getInstance() — это возможность C++11, называемая «magic statics» (магические статические переменные): она потокобезопасна и гарантированно инициализируется только один раз. Удаляя конструктор копирования и оператор присваивания, мы предотвращаем создание копий нашего синглтона.
Используйте Singleton с осторожностью — он вводит глобальное состояние, которое может усложнить тестирование. Он лучше всего подходит для действительно общих ресурсов, где наличие нескольких экземпляров может привести к проблемам.
Задание
ЛегкоДавайте создадим синглтон GameSettings, который управляет конфигурацией игрового приложения. Это идеальный вариант использования паттерна Синглтон — вам нужен ровно один менеджер настроек, к которому могут обращаться все части вашей игры, обеспечивая согласованность конфигурации во всем приложении.
Вы распределите свой код по трем файлам:
GameSettings.h: Определите свой класс-синглтон, который управляет конфигурацией игры.Ваш класс
GameSettingsдолжен хранить три настройки в качестве приватных членов:difficulty(строка),volume(целое число от 0 до 100) иfullscreen(логическое значение). Инициализируйте их значениями по умолчанию:"Normal",50иfalse.Реализуйте паттерн Синглтон правильно:
- Сделайте конструктор приватным
- Удалите конструктор копирования и оператор присваивания
- Предоставьте статический метод
getInstance(), который возвращает ссылку на единственный экземпляр
Добавьте эти публичные методы для взаимодействия с настройками:
setDifficulty(const std::string& diff)— обновляет сложностьsetVolume(int vol)— обновляет громкостьsetFullscreen(bool fs)— обновляет полноэкранный режимdisplaySettings()— выводит все текущие настройки
GameModule.h: Создайте отдельный модуль, который обращается к синглтону.Определите функцию с именем
applyGamePreset, которая принимает название пресета (строку). Эта функция должна обращаться к синглтонуGameSettingsи настраивать его на основе пресета:"casual"— устанавливает сложность в"Easy", громкость в70, полноэкранный режим вfalse"competitive"— устанавливает сложность в"Hard", громкость в30, полноэкранный режим вtrue- Любой другой пресет — оставляет настройки по умолчанию без изменений
Это демонстрирует, как разные части программы могут получать доступ к одному и тому же экземпляру синглтона и изменять его.
main.cpp: Соберите все вместе, чтобы показать синглтон в действии.Прочитайте два входных значения:
- Название пресета (строка)
- Пользовательский уровень громкости (целое число)
Сначала отобразите начальные настройки по умолчанию, вызвав
displaySettings()у синглтона. После этого выведите пустую строку.Затем примените пресет с помощью функции
applyGamePresetи снова отобразите настройки. После этого выведите пустую строку.Наконец, переопределите только громкость, используя введенное пользовательское значение, и отобразите окончательные настройки.
Метод displaySettings() должен выводить данные именно в таком формате:
Difficulty: [value]
Volume: [value]
Fullscreen: [yes/no]Например, при входных данных casual и 85:
Difficulty: Normal
Volume: 50
Fullscreen: no
Difficulty: Easy
Volume: 70
Fullscreen: no
Difficulty: Easy
Volume: 85
Fullscreen: noПри входных данных competitive и 20:
Difficulty: Normal
Volume: 50
Fullscreen: no
Difficulty: Hard
Volume: 30
Fullscreen: yes
Difficulty: Hard
Volume: 20
Fullscreen: yesОбратите внимание, как и main.cpp, и GameModule.h обращаются к одному и тому же экземпляру синглтона — изменения, внесенные в одном месте, видны повсюду. В этом и заключается сила паттерна Синглтон для общих ресурсов, таких как настройки игры.
Шпаргалка
Паттерн Singleton (Одиночка) гарантирует, что у класса есть только один экземпляр на протяжении всей программы, и предоставляет к нему глобальную точку доступа. Он полезен для общих ресурсов, таких как менеджеры конфигурации, логгеры или пулы соединений с базой данных.
Ключевые шаги реализации:
- Сделайте конструктор приватным, чтобы предотвратить прямое создание экземпляров
- Удалите конструктор копирования и оператор присваивания, чтобы предотвратить копирование
- Предоставьте статический метод, который возвращает ссылку на единственный экземпляр
class Logger {
private:
std::string logFile;
// Приватный конструктор
Logger() : logFile("app.log") {}
// Удаляем конструктор копирования и оператор присваивания
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
public:
// Статический метод для доступа к единственному экземпляру
static Logger& getInstance() {
static Logger instance; // Создается один раз, живет до завершения программы
return instance;
}
void log(const std::string& message) {
std::cout << "[LOG] " << message << "\n";
}
};
// Использование
Logger::getInstance().log("Application started");
Logger& logger = Logger::getInstance();
logger.log("Processing data");Строка static Logger instance внутри getInstance() использует «магические статики» C++11 — это потокобезопасно и гарантирует инициализацию только один раз.
Используйте Singleton с осторожностью, так как он вводит глобальное состояние, что может усложнить тестирование. Он лучше всего подходит для действительно общих ресурсов, где наличие нескольких экземпляров может вызвать проблемы.
Попробуйте сами
#include <iostream>
#include <string>
#include "GameSettings.h"
#include "GameModule.h"
using namespace std;
int main() {
// Чтение входных данных
string presetName;
int customVolume;
cin >> presetName;
cin >> customVolume;
// TODO: Получить экземпляр синглтона `GameSettings`
// TODO: Отобразить начальные настройки по умолчанию
// TODO: Вывести пустую строку
// TODO: Применить пресет с помощью функции `applyGamePreset`
// TODO: Отобразить настройки после применения пресета
// TODO: Вывести пустую строку
// TODO: Переопределить громкость с помощью `customVolume`
// TODO: Отобразить итоговые настройки
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер