Menu
Coddy logo textTech

Паттерн 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 экономно — он вводит глобальное состояние, которое может усложнить тестирование. Лучше всего он подходит для действительно общих ресурсов, где наличие нескольких экземпляров могло бы вызвать проблемы.

challenge icon

Задание

Легко

Давайте создадим синглтон 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: объедините всё, чтобы показать синглтон в действии.

    Считайте два входных значения:

    1. Название пресета (строка)
    2. Пользовательский уровень громкости (целое число)

    Сначала выведите исходные настройки по умолчанию, вызвав 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;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор C++