Паттерн 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(), вы всегда получаете один и тот же объект.
Задание
ЛегкоДавайте создадим модуль 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 работает, изменения, внесенные через один указатель, будут видны через другой.
Ваша программа получит два входных значения:
- Новое значение громкости
- Новое значение яркости
Выведите громкость и яркость на отдельных строках после их изменения через один указатель и чтения через другой.
Пример вывода, когда входные данные 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;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Основы модульного программирования
Заголовочные файлыСтражи включенияИсходные файлыСтатические функцииПовторение: Модульный калькулятор4Инкапсуляция
Концепция непрозрачных указателейОпределение непрозрачных структурГеттеры и сеттерыВалидация в сеттерахИтоги: Секретный ящик2Объекты и методы
Структуры как объектыУказатель 'Self'Константная корректностьУказатель против значенияВспомогательные методыИтоги: Point Manager5Проект: Простой банковский счет
Настройка проектаРеализация счета3Жизненный цикл объекта
Паттерн «Конструктор»Паттерн «Деструктор»Инициализация в стекеГлубокое копированиеПовторение: String Wrapper6Наследование через композицию
Встраивание структурПравило первого элементаДоступ к элементам родителяUpcastingПовторение: Иерархия фигур9Проект: Рисование фигур
Обзор проектаРеализация круга