Шаблон Singleton гарантирует один экземпляр класса и единую точку доступа к общим ресурсам. В этом руководстве — когда применять Singleton, как реализовать его безопасно в TypeScript и какие есть современные альтернативы для улучшения тестируемости и гибкости.
January 19, 2026 (2mo ago) — last updated March 15, 2026 (29d ago)
Singleton в TypeScript: руководство для разработчика
Полное руководство по Singleton: когда применять, пример на TypeScript, влияние на тестирование и лучшие альтернативы — DI и IoC.
← Back to blog
Освоение шаблона Singleton в TypeScript: Полное руководство

В мире разработки есть инструменты, которые мощны, но требуют осторожности. Шаблон Singleton гарантирует, что у класса есть только один экземпляр и что к нему есть единая точка доступа — простая идея с важными архитектурными последствиями1.
Думайте о Singleton как о центральном менеджере конфигурации или общем сервисе логирования: он предотвращает дублирование, экономит память и упрощает контроль над общими ресурсами.
Что такое Singleton и зачем он нужен?
Представьте королевство с единственным Королевским Писцом: он обеспечивает согласованность и порядок. В приложении Singleton выполняет ту же задачу — обеспечивает единственный источник истины для ресурса, который должен быть уникальным.
Коротко о ключевых свойствах
| Характеристика | Описание |
|---|---|
| Единственный экземпляр | Конструктор скрыт, доступ через статический метод. |
| Глобальная точка доступа | Обычно реализуется как метод getInstance(). |
| Ленивое создание | Экземпляр создаётся при первом запросе, что экономит ресурсы. |
| Централизованное состояние | Подходит для настроек, логирования, управления оборудованием. |
Когда Singleton оправдан
Типичные сценарии:
- Сервисы логирования, где важна одна точка записи.
- Централизованная конфигурация приложения.
- Доступ к редкому аппаратному интерфейсу.
Но помните: простота Singleton часто меняется на скрытую связанность и проблемы с тестированием3.
Преимущества и недостатки

Преимущества
- Упрощённый доступ к общему ресурсу.
- Экономия памяти и ресурсов при ленивой инициализации.
- Уменьшение дублирования тяжёлых объектов.
Недостатки
- Скрытая зависимость, которая не видна в сигнатурах методов.
- Глобальное изменяемое состояние ведёт к сложноуловимым багам.
- Трудности с модульным тестированием и мокированием.
Singleton обменивает простоту на глобальное состояние, поэтому используйте его осмотрительно.
Влияние на тестирование и связанность
Singleton затрудняют изоляцию тестов, потому что состояние может просачиваться между тестами. Поэтому современные команды предпочитают внедрение зависимостей: оно делает зависимости явными и легко заменяемыми в тестах3.
Лучшие практики при использовании Singleton
- Используйте Singleton только для действительно уникальных ресурсов.
- Делайте ленивую инициализацию и учитывайте вопросы конкурентности и потокобезопасности.
- По возможности объявляйте интерфейсы и обеспечьте возможность замены реализации в тестах.
Реализация Singleton в TypeScript
Перейдём к практическому примеру: создадим типобезопасный ConfigManager. TypeScript позволяет закрыть конструктор и управлять доступом через статический метод2.
class ConfigManager {
private static instance: ConfigManager;
private settings: Map<string, any> = new Map();
private constructor() {
console.log(“Инициализация ConfigManager…”);
this.settings.set(“API_URL”, “https://api.example.com”);
this.settings.set(“TIMEOUT”, 5000);
}
public static getInstance(): ConfigManager {
if (!ConfigManager.instance) {
ConfigManager.instance = new ConfigManager();
}
return ConfigManager.instance;
}
public get(key: string): any {
return this.settings.get(key);
}
}
// Ошибка: constructor приватный
// const config = new ConfigManager();
Пример применения
class ApiService {
private apiUrl: string;
constructor() {
const config = ConfigManager.getInstance();
this.apiUrl = config.get(“API_URL”);
console.log(`ApiService initialized with API URL: ${this.apiUrl}`);
}
public fetchData(): void {
console.log(`Fetching data from ${this.apiUrl}...`);
}
}
console.log(“Application starting…”);
const service1 = new ApiService();
service1.fetchData();
const service2 = new ApiService();
console.log(“Application finished.”);
При запуске инициализация ConfigManager должна произойти только один раз.
Почему у Singleton плохая репутация
Основные претензии: скрытая связанность, глобальное изменяемое состояние и осложнённое тестирование. Singletonы могут распространяться по кодовой базе и усложнять рефакторинг3.
Конкуренция и состояние гонки
Общий счётчик или сессия, управляемые Singleton, уязвимы к состояниям гонки в многопоточных или асинхронных сценариях без соответствующей синхронизации.
Проблемы тестирования
Singleton делают мокирование и изоляцию сложнее, поскольку тестам приходится очищать или переинициализировать глобальное состояние.
Современные альтернативы
Внедрение зависимостей (DI) — предпочтительный подход: зависимости делаются явными и легко заменяются в тестах. Многие TypeScript-фреймворки поддерживают DI из коробки, а библиотеки вроде InversifyJS предоставляют гибкие IoC-контейнеры45.
Пример с DI
interface IConfigManager {
get(key: string): any;
}
class ApiService {
private apiUrl: string;
constructor(config: IConfigManager) {
this.apiUrl = config.get(“API_URL”);
}
}
Теперь ApiService зависит от контракта, и в тестах можно подставлять фейковые реализации.
Как рефакторить Singleton в унаследованной базе
Работайте инкрементально:
- Найдите все вызовы getInstance().
- Определите интерфейс с нужными методами.
- Рефакторите потребителей, чтобы они принимали интерфейс через конструктор.
- Создавайте и передавайте один экземпляр в корне композиции или используйте DI-контейнер.
Такая стратегия сохраняет поведение, снижая скрытую связанность.
Вопросы и ответы — быстрые рекомендации
В: Всегда ли Singleton — плохая идея? О: Нет. Он уместен для действительно уникальных и контролируемых ресурсов, например централизованного логгера или адаптера к оборудованию. Даже тогда рассмотрите DI и контролируемую точку композиции.
В: Как минимизировать проблемы с тестированием? О: Введите интерфейс, рефакторите потребителей на приём зависимости через конструктор и используйте тестовые заглушки; делайте это поэтапно.
В: Как избежать состояния гонки в Singleton? О: Добавьте синхронизацию при доступе к общему состоянию или используйте механизмы языка/фреймворка для безопасной инициализации.
Краткие практические выводы
- Используйте Singleton экономно и только там, где объект по сути уникален.
- Предпочитайте явное внедрение зависимостей для лучшей тестируемости.
- Рефакторьте унаследованный код шаг за шагом: интерфейсы → конструкторные зависимости → корневое создание экземпляра.
- Для новых проектов рассмотрите DI-контейнер вместо статических Singletons — это даёт контроль жизненного цикла без скрытой связанности.
Дополнительные быстрые Q&A (короткие ответы)
Q: Как отличить подходящий случай для Singleton? A: Если ресурс действительно единственный по природе и имеет ограниченный жизненный цикл, Singleton может быть оправдан.
Q: Можно ли тестировать код с Singleton без рефакторинга? A: Можно, но не удобно: придётся вручную сбрасывать глобальное состояние или использовать специальные тестовые хелперы.
Q: Как начать миграцию от Singleton к DI в крупном коде? A: Рефакторите один модуль за раз: введите интерфейсы и передавайте зависимости через конструкторы, сохраняя текущий Singleton в корне до завершения миграции.
ИИ пишет код.Вы делаете его долговечным.
В эпоху ускорения ИИ чистый код — это не просто хорошая практика — это разница между системами, которые масштабируются, и кодовыми базами, которые рушатся под собственным весом.