Menu
Coddy logo textTech

Паттерн Singleton

Часть раздела Object Oriented Programming путешествия по C на Coddy — урок 54 из 61.

Иногда вам нужен ровно один экземпляр объекта во всей программе — менеджер конфигурации, система логирования или подключение к базе данных. Паттерн Одиночка (Singleton) гарантирует, что существует только один экземпляр, и предоставляет к нему глобальную точку доступа.

В языке C мы реализуем это с помощью переменной static внутри исходного файла. Помните, что static в области видимости файла означает, что переменная является приватной для этого файла и сохраняется в течение всего времени работы программы. В сочетании с функцией-геттером это создает наш Singleton:

// config.c
#include "config.h"
#include <stdlib.h>

struct Config {
    int max_connections;
    int timeout;
};

static Config* instance = NULL;

Config* get_config(void) {
    if (instance == NULL) {
        instance = malloc(sizeof(Config));
        instance->max_connections = 100;
        instance->timeout = 30;
    }
    return instance;
}

Магия происходит в get_config(). Первый вызов создает и инициализирует экземпляр. Каждый последующий вызов возвращает тот же указатель.

Ключевое слово static гарантирует, что instance сохраняет свое значение между вызовами функций и остается скрытым от других файлов.

Из заголовка пользователи видят только функцию-геттер — они не могут получить доступ к статической переменной или изменить её напрямую. Независимо от того, сколько раз или откуда вы вызываете get_config(), вы всегда получаете один и тот же объект.

challenge icon

Задание

Легко

Давайте создадим модуль Settings, который управляет конфигурацией всего приложения, используя паттерн Одиночка (Singleton). Независимо от того, сколько раз ваш код запрашивает настройки, он всегда должен получать один и тот же экземпляр.

Вы организуете свой код в трех файлах:

  • settings.h: Объявите непрозрачный тип Settings, используя опережающее объявление (тело структуры остается скрытым). Объявите функцию get_settings, которая возвращает указатель на экземпляр-одиночку, а также функции геттеры и сеттеры для двух значений конфигурации: volume (целое число) и brightness (целое число). Здесь обязательны include guards.
  • settings.c: Здесь происходит основная логика. Определите саму структуру Settings с полями volume и brightness. Создайте static переменную-указатель для хранения единственного экземпляра (инициализируйте её значением NULL). Реализуйте get_settings так, чтобы при первом вызове выделялась память и экземпляр инициализировался значениями по умолчанию (volume: 50, brightness: 75), а последующие вызовы просто возвращали существующий экземпляр. Реализуйте геттеры и сеттеры для доступа и изменения скрытых полей.
  • main.c: Продемонстрируйте, что Singleton работает правильно. Считайте два целых числа, представляющих новые значения громкости и яркости. Вызовите get_settings дважды, сохранив результаты в двух разных переменных-указателях. Используйте первый указатель для обновления громкости и яркости входными значениями. Затем используйте второй указатель, чтобы извлечь и напечатать оба значения. Если Singleton работает, изменения, внесенные через один указатель, будут видны через другой.

Ваша программа получит два входных значения:

  1. Новое значение громкости
  2. Новое значение яркости

Выведите громкость и яркость на отдельных строках после их изменения через один указатель и чтения через другой.

Пример вывода, когда входные данные 80 и 100:

80
100

Пример вывода, когда входные данные 25 и 60:

25
60

Пример вывода, когда входные данные 0 и 50:

0
50

Ключевая идея заключается в том, что оба указателя указывают на один и тот же объект в памяти — в этом суть паттерна Singleton. Статическая переменная внутри вашего исходного файла гарантирует, что существует только один экземпляр, и он сохраняется в течение всего времени работы программы.

Шпаргалка

Паттерн Одиночка (Singleton) гарантирует, что на протяжении всей работы программы существует только один экземпляр объекта, и предоставляет к нему глобальную точку доступа.

В языке C Одиночка реализуется с помощью переменной static внутри исходного файла в сочетании с функцией-геттером:

// config.c
#include "config.h"
#include <stdlib.h>

struct Config {
    int max_connections;
    int timeout;
};

static Config* instance = NULL;

Config* get_config(void) {
    if (instance == NULL) {
        instance = malloc(sizeof(Config));
        instance->max_connections = 100;
        instance->timeout = 30;
    }
    return instance;
}

Ключевые моменты:

  • Ключевое слово static в области видимости файла делает переменную приватной для этого файла и сохраняет её на протяжении всего времени работы программы
  • Функция-геттер при первом вызове проверяет, равен ли экземпляр NULL, создает и инициализирует его, а затем возвращает
  • Последующие вызовы возвращают тот же самый указатель
  • Пользователи видят только функцию-геттер через заголовочный файл — они не могут напрямую получить доступ к статической переменной или изменить её
  • Независимо от того, сколько раз и откуда вы вызываете геттер, вы всегда получаете один и тот же объект

Попробуйте сами

#include <stdio.h>
#include "settings.h"

int main() {
    int new_volume, new_brightness;
    scanf("%d", &new_volume);
    scanf("%d", &new_brightness);
    
    // TODO: Вызовите get_settings дважды и сохраните в двух разных переменных-указателях
    
    
    // TODO: Используйте первый указатель, чтобы обновить volume и brightness входными значениями
    
    
    // TODO: Используйте второй указатель, чтобы получить и вывести volume и brightness
    // (Это демонстрирует, что оба указателя указывают на один и тот же экземпляр singleton)
    
    
    return 0;
}
quiz iconПроверьте себя

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

Все уроки раздела Object Oriented Programming