January 19, 2026 (7mo ago) — last updated June 27, 2026 (2mo ago)

Singleton у TypeScript: повний посібник

Повний посібник по патерну Singleton в TypeScript: коли застосовувати, приклади коду, недоліки й сучасні альтернативи для кращої архітектури та тестування.

← Back to blog
Cover Image for Singleton у TypeScript: повний посібник

Патерн Singleton гарантує один екземпляр класу і є єдиною точкою доступу до спільного ресурсу. У цій статті — приклади реалізації в TypeScript, переваги й ризики, а також сучасні альтернативи для кращої тестованості.

Singleton у TypeScript: повний посібник

Ескіз олівцем королівського писаря в короні, який старанно вивчає світлий сувій на столі.

У світі розробки деякі інструменти потужні, але їх варто застосовувати обачно. Патерн Singleton гарантує, що клас має лише один екземпляр і надає єдину, глобальну точку доступу до нього. Це корисно для централізованих сервісів, але водночас несе ризики прихованих залежностей і ускладненого тестування1.

Що таке Singleton і коли його застосовувати

Уявіть королівський реєстр з одним офіційним писарем: це гарантує послідовність записів. У програмуванні Singleton обмежує створення екземплярів класу одним об’єктом, який стає джерелом істини для певного ресурсу. Це добре підходить для сервісів логування, менеджменту конфігурації або доступу до апаратного інтерфейсу, де багатократні екземпляри спричиняють конфлікти.

Основні риси

  • Єдиний екземпляр — зазвичай через приватний конструктор.
  • Глобальна точка доступу — статичний метод типу getInstance().
  • Лінива ініціалізація — екземпляр створюється під час першого запиту.
  • Централізоване керування станом — підходить для унікальних ресурсів.

Одна проста ідея: один клас, один екземпляр, одна загальнодоступна точка доступу.

Практичні випадки використання

  • Сервіси логування — один екземпляр логера для всього додатка.
  • Менеджмент конфігурації — єдине джерело налаштувань.
  • Доступ до апаратного інтерфейсу — щоб уникнути конфліктних команд.

Переваги та недоліки

Ваги, що порівнюють структуровані, спостережувані патерни дизайну з складним, заплутаним і експериментальним кодом.

Переваги

  • Простий глобальний доступ між модулями.
  • Економія ресурсів завдяки лінивій ініціалізації.
  • Менше дублювання дорогих ресурсів.

Недоліки

  • Тісна зв’язаність і приховані залежності.
  • Глобальний змінний стан, який ускладнює відлагодження.
  • Складнощі з модульним тестуванням та підміною під час тестів3.

Вплив на тестування

Singleton ускладнює модульні тести: стан може просочуватися між прогонами, і підміна екземпляра стає незручною. Через це багато команд віддають перевагу впровадженню залежностей, що робить залежності явними і легкими для заміни3.

Ключові висновки

  • Використовуйте Singleton лише для дійсно унікальних сервісів.
  • Віддавайте перевагу впровадженню залежностей для кращої декуплінгу і тестованості.
  • Якщо використовуєте Singleton, реалізуйте ліниву ініціалізацію і будьте уважні до конкурентності.

Як реалізувати Singleton у TypeScript

Діаграма, що показує ConfigManager із замком, демонструючи патерн Singleton за допомогою getInstance у TypeScript.

Перейдемо до практики: у TypeScript ключові елементи — приватний конструктор і статичний метод, що повертає єдиний екземпляр. Нижче наведено приклад ConfigManager, який завантажує налаштування додатка.

Приклад: типобезпечний ConfigManager

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);
  }
}

Цей підхід простий, типобезпечний і підходить для базових потреб. Більш складні вимоги конкурентності потребують додаткової синхронізації.

Приклад використання у сервісі

class ApiService {
  private apiUrl: string;

  constructor() {
    const config = ConfigManager.getInstance();
    this.apiUrl = config.get("API_URL");
    console.log(`ApiService ініціалізовано з API URL: ${this.apiUrl}`);
  }

  public fetchData(): void {
    console.log(`Отримання даних з ${this.apiUrl}...`);
  }
}

console.log("Запуск додатка...");

const service1 = new ApiService();
service1.fetchData();

const service2 = new ApiService();
console.log("Додаток завершив роботу.");

Чому Singleton має погану репутацію

Проблеми виникають через приховані залежності, глобальний стан і складність тестування. Singleton зі станом може спричиняти умови гонки в конкурентних середовищах, наприклад при одночасному інкременті лічильника сесій. Баги, пов’язані з такими станами, часто важко відтворити.

Сучасні альтернативи

Найпоширеніша альтернатива — впровадження залежностей (Dependency Injection, DI). DI робить залежності явними, полегшує тестування і дозволяє контролювати життєвий цикл обʼєктів через IoC-контейнери45.

Впровадження залежностей замість Singleton

Початковий ApiService, який звертається до Singleton, можна переписати так, щоб він приймав менеджера конфігурації через конструктор.

interface IConfigManager {
  get(key: string): any;
}

class ApiService {
  private apiUrl: string;

  constructor(config: IConfigManager) {
    this.apiUrl = config.get("API_URL");
  }
}

Тепер ApiService залежить від контракту, і під час тестів можна передати фейковий менеджер. IoC-контейнери надають конфігурацію життєвого циклу: transient, scoped або singleton-подібний, але без прихованої статичної точки доступу.

Детальніше дивіться у розділі «Сучасні альтернативи» або в документації фреймворків, які підтримують DI45.

Рефакторинг Singleton у спадковому коді

Працюйте поступово:

  1. Знайдіть виклики MySingleton.getInstance() і опишіть інтерфейс з потрібними методами.
  2. Рефакторьте одного споживача за раз, щоб він приймав інтерфейс через конструктор.
  3. На корені композиції створіть і передавайте один екземпляр або дозвольте DI-контейнеру керувати ним.

Такий підхід мінімізує регресії і дозволяє поступово позбутися статичного доступу.

Швидкі питання й відповіді

Q: Коли доречно використовувати Singleton?

A: Коли сервіс дійсно унікальний і безстанний, наприклад центральний логер або апаратний адаптер.

Q: Як зберегти тестованість, якщо вже є Singleton?

A: Введіть інтерфейс, рефакторьте споживачів для інжекції залежностей і підміняйте реалізацію під час тестів.

Q: Чи можна отримати ті самі переваги без статичного Singleton?

A: Так. DI-контейнери дозволяють контролювати життєвий цикл і забезпечують один екземпляр без прихованої зв’язаності.

FAQ — Короткі питання й відповіді

Q: Чи завжди Singleton — погана ідея? A: Не завжди. Він підходить для дійсно унікальних, безстанних сервісів. Проте DI частіше є кращим вибором для підтримуваності.

Q: Як тестувати код, що зараз використовує Singleton? A: Введіть інтерфейс, рефакторьте споживачів, щоб вони приймали інтерфейс, і інжектуйте тестову підстановку поступово.

Q: Який шлях видалити Singleton зі спадкового додатка? A: Визначте інтерфейси, рефакторьте споживачів для інжекції залежностей, потім створюйте й інжектуйте один екземпляр у корені або використайте DI-контейнер.


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/
← Back to blog
🙋🏻‍♂️

ШІ пише код.
Ви робите його довговічним.

В епоху прискорення ШІ чистий код — це не просто хороша практика — це різниця між системами, які масштабуються, та кодовими базами, які руйнуються під власною вагою.

Singleton у TypeScript: повний посібник | Clean Code Guy