January 19, 2026 (2mo ago) — last updated March 15, 2026 (29d ago)

Singleton в TypeScript: руководство для разработчика

Полное руководство по Singleton: когда применять, пример на TypeScript, влияние на тестирование и лучшие альтернативы — DI и IoC.

← Back to blog
Cover Image for Singleton в TypeScript: руководство для разработчика

Шаблон Singleton гарантирует один экземпляр класса и единую точку доступа к общим ресурсам. В этом руководстве — когда применять Singleton, как реализовать его безопасно в TypeScript и какие есть современные альтернативы для улучшения тестируемости и гибкости.

Освоение шаблона 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 в унаследованной базе

Работайте инкрементально:

  1. Найдите все вызовы getInstance().
  2. Определите интерфейс с нужными методами.
  3. Рефакторите потребителей, чтобы они принимали интерфейс через конструктор.
  4. Создавайте и передавайте один экземпляр в корне композиции или используйте DI-контейнер.

Такая стратегия сохраняет поведение, снижая скрытую связанность.

Вопросы и ответы — быстрые рекомендации

В: Всегда ли Singleton — плохая идея? О: Нет. Он уместен для действительно уникальных и контролируемых ресурсов, например централизованного логгера или адаптера к оборудованию. Даже тогда рассмотрите DI и контролируемую точку композиции.

В: Как минимизировать проблемы с тестированием? О: Введите интерфейс, рефакторите потребителей на приём зависимости через конструктор и используйте тестовые заглушки; делайте это поэтапно.

В: Как избежать состояния гонки в Singleton? О: Добавьте синхронизацию при доступе к общему состоянию или используйте механизмы языка/фреймворка для безопасной инициализации.

Краткие практические выводы

  • Используйте Singleton экономно и только там, где объект по сути уникален.
  • Предпочитайте явное внедрение зависимостей для лучшей тестируемости.
  • Рефакторьте унаследованный код шаг за шагом: интерфейсы → конструкторные зависимости → корневое создание экземпляра.
  • Для новых проектов рассмотрите DI-контейнер вместо статических Singletons — это даёт контроль жизненного цикла без скрытой связанности.

Дополнительные быстрые Q&A (короткие ответы)

Q: Как отличить подходящий случай для Singleton? A: Если ресурс действительно единственный по природе и имеет ограниченный жизненный цикл, Singleton может быть оправдан.

Q: Можно ли тестировать код с Singleton без рефакторинга? A: Можно, но не удобно: придётся вручную сбрасывать глобальное состояние или использовать специальные тестовые хелперы.

Q: Как начать миграцию от Singleton к DI в крупном коде? A: Рефакторите один модуль за раз: введите интерфейсы и передавайте зависимости через конструкторы, сохраняя текущий Singleton в корне до завершения миграции.


1.
Martin Fowler, “Singleton,” Bliki. https://martinfowler.com/bliki/Singleton.html
2.
3.
Mark Seemann, “Singletons Are Pathological Liars.” https://blog.ploeh.dk/2010/07/28/SingletonsArePathologicalLiar/
4.
NestJS Documentation, “Dependency Injection.” https://docs.nestjs.com/fundamentals/injection
5.
InversifyJS Documentation. https://inversify.io/
6.
Stack Overflow, Developer Survey 2023 — обзор технологий и трендов. https://insights.stackoverflow.com/survey/2023
7.
Wikipedia, “Dependency injection.” https://en.wikipedia.org/wiki/Dependency_injection
← Back to blog
🙋🏻‍♂️

ИИ пишет код.
Вы делаете его долговечным.

В эпоху ускорения ИИ чистый код — это не просто хорошая практика — это разница между системами, которые масштабируются, и кодовыми базами, которые рушатся под собственным весом.