Паттерн Singleton
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 91 из 104.
Паттерн Singleton гарантирует, что у класса будет только один экземпляр на протяжении всей программы, и предоставляет глобальную точку доступа к нему. Это полезно для ресурсов, которыми следует делиться, например менеджера конфигурации, ведения журнала или пула подключений к базе данных.
Ключевая идея заключается в том, чтобы сделать конструктор закрытым, чтобы никто не мог создавать экземпляры напрямую, а затем предоставить статический метод, возвращающий единственный экземпляр:
#include <iostream>
#include <string>
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";
}
};
int main() {
Logger::getInstance().log("Application started");
Logger& logger = Logger::getInstance();
logger.log("Processing data");
}static Logger instance внутри getInstance() — это функция C++11, называемая «магическими статическими переменными»: она безопасна для потоков и гарантированно инициализируется только один раз. Удаляя конструктор копирования и оператор присваивания, мы не позволяем создавать копии нашего синглтона.
Используйте Singleton экономно — он вводит глобальное состояние, которое может усложнить тестирование. Лучше всего он подходит для действительно общих ресурсов, где наличие нескольких экземпляров могло бы вызвать проблемы.
Задание
ЛегкоДавайте создадим синглтон GameSettings, который управляет конфигурацией игрового приложения. Это идеальный вариант использования паттерна Singleton. Вам нужен ровно один менеджер настроек, к которому могут обращаться все части игры, обеспечивая согласованную конфигурацию повсюду.
Вы организуете код в трёх файлах:
GameSettings.h: определите свой класс синглтона, который управляет конфигурацией игры.Ваш класс
GameSettingsдолжен хранить три настройки в качестве закрытых членов:difficulty(строка),volume(целое число от 0 до 100) иfullscreen(логическое значение). Инициализируйте их значениями по умолчанию:"Normal",50иfalse.Правильно реализуйте паттерн Singleton:
- Сделайте конструктор закрытым
- Удалите конструктор копирования и оператор присваивания
- Предоставьте статический метод
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 для общих ресурсов, таких как настройки игры.
Попробуйте сами
#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Итоги — Обобщенный контейнерПотренируйтесь самостоятельно: Онлайн-компилятор C++