Шаблон Singleton гарантирует один экземпляр класса и единую точку доступа к общим ресурсам, но он также может создавать скрытые зависимости и осложнять тестирование1. В этом руководстве разберём, когда Singleton оправдан, как безопасно реализовать его в TypeScript и какие современные альтернативы — внедрение зависимостей и контейнеры IoC — стоит предпочесть в новых проектах26.
January 19, 2026 (3mo ago) — last updated April 26, 2026 (4d ago)
Singleton в TypeScript: практическое руководство
Практическое руководство по Singleton в TypeScript: реализация, влияние на тестирование и лучшие альтернативы — DI и IoC.
← Back to blog
Освоение шаблона Singleton в TypeScript: полное руководство

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

Преимущества
- Упрощённый доступ к общему ресурсу.
- Экономия памяти и ресурсов при ленивой инициализации.
- Снижение дублирования тяжёлых объектов.
Недостатки
- Скрытые зависимости, не отражаемые в сигнатурах.
- Глобальное изменяемое состояние приводит к сложноуловимым багам.
- Трудности с модульным тестированием и мокированием.
Используйте Singleton осторожно: он даёт простоту доступа, но может усложнить развитие и тестирование проекта.
Влияние на тестирование и связанность
Singleton затрудняют изоляцию тестов, потому что состояние может просачиваться между тестами. Современные команды предпочитают внедрение зависимостей: зависимости становятся явными и легко заменяемыми в тестах37.
Лучшие практики при использовании Singleton
- Применяйте Singleton только для действительно уникальных ресурсов.
- Делайте ленивую инициализацию и учитывайте вопросы конкурентности.
- Объявляйте интерфейсы и обеспечьте возможность замены реализации в тестах.
- По возможности создавайте экземпляр в корне приложения и передавайте его через конструкторы.
Реализация Singleton в TypeScript
Ниже — типобезопасный пример ConfigManager с приватным конструктором и ленивой инициализацией.
class ConfigManager {
private static instance: ConfigManager | null = null;
private settings = new Map<string, any>();
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 === null) {
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 хранит изменяемое состояние, он уязвим к состояниям гонки в многопоточном или асинхронном окружении без синхронизации.
Проблемы тестирования
Мокирование и изоляция тестов усложняются: тестам приходится сбрасывать или переинициализировать глобальные состояния.
Современные альтернативы
Внедрение зависимостей (DI) — предпочтительный подход: зависимости явные и легко заменяемые в тестах. Многие TypeScript-фреймворки поддерживают DI, а библиотеки вроде InversifyJS предлагают гибкие IoC-контейнеры457. Согласно обзорам отрасли, практика DI набирает популярность как способ повышения тестируемости и гибкости кода6.
Пример с 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 — плохая идея?
A: Нет. Он пригоден для действительно уникальных и контролируемых ресурсов, например централизованного логгера или адаптера к оборудованию. Даже тогда предпочитайте контролируемое создание экземпляра в корне и возможность подмены в тестах.
В: Как минимизировать проблемы с тестированием?
A: Введите интерфейс, перестройте потребителей на приём зависимости через конструктор и используйте тестовые заглушки; делайте это поэтапно.
В: Как избежать состояния гонки в Singleton?
A: Ограничьте изменяемое состояние, добавьте синхронизацию при доступе или используйте примитивы языка и фреймворка для безопасной инициализации.
Краткие практические выводы
- Используйте Singleton экономно и только там, где объект по сути уникален.
- Предпочитайте явное внедрение зависимостей для лучшей тестируемости.
- Рефакторьте унаследованный код шаг за шагом: интерфейсы → конструкторные зависимости → корневое создание экземпляра.
- Для новых проектов рассмотрите DI-контейнер вместо статических Singleton — это даёт контроль жизненного цикла без скрытой связанности.
Дополнительные быстрые Q&A (короткие ответы)
Q: Как отличить подходящий случай для Singleton?
A: Если ресурс действительно единственный по природе и имеет ограниченный жизненный цикл, Singleton может быть оправдан.
Q: Можно ли тестировать код с Singleton без рефакторинга?
A: Можно, но неудобно: придётся вручную сбрасывать глобальное состояние или использовать специальные тестовые хелперы.
Q: Как начать миграцию от Singleton к DI в крупном коде?
A: Рефакторите один модуль за раз: вводите интерфейсы и передавайте зависимости через конструкторы, сохраняя текущий Singleton в корне до завершения миграции.
ИИ пишет код.Вы делаете его долговечным.
В эпоху ускорения ИИ чистый код — это не просто хорошая практика — это разница между системами, которые масштабируются, и кодовыми базами, которые рушатся под собственным весом.