Poznaj wzorzec projektowy Singleton: kiedy go używać, praktyczne przykłady w TypeScript i niezbędne alternatywy.
January 19, 2026 (3mo ago)
Opanowanie wzorca projektowego Singleton: Kompletny przewodnik dla dewelopera
Poznaj wzorzec projektowy Singleton: kiedy go używać, praktyczne przykłady w TypeScript i niezbędne alternatywy.
← Back to blog
Opanowanie wzorca Singleton w TypeScript: Kompletny przewodnik

W świecie tworzenia oprogramowania niektóre narzędzia są potężne, ale wymagają ostrożnego stosowania. Wzorzec Singleton jest jednym z nich. W swojej istocie to prosta koncepcja: zapewnić, że dana klasa ma tylko jedną instancję i udostępnić pojedynczy, globalny sposób dostępu do niej.1
Pomyśl o nim jak o centralnym menedżerze konfiguracji lub dedykowanej usłudze logowania dla całej aplikacji. Nie chciałbyś, żeby po aplikacji krążyło wiele sprzecznych obiektów konfiguracji, ani żeby wpisy logów były rozproszone po różnych plikach przez konkurujące instancje loggerów. Singleton wprowadza porządek, zapobiegając tym duplikatom, oszczędzając pamięć i unikając chaosu. To podstawowy wzorzec do zarządzania dostępem do zasobów współdzielonych.
Czym jest wzorzec Singleton i kiedy jest użyteczny?
Wyobraź sobie średniowieczne królestwo z tylko jednym oficjalnym Królewskim Pisarzem. Ta osoba jest jedyną upoważnioną do zapisywania królewskich dekretów. Zapewnia to, że każde prawo i ogłoszenie jest spójne, właściwie upoważnione i przechowywane w jednym, ostatecznym rejestrze. Gdyby ktokolwiek mógł zdecydować, że będzie pisarzem, królestwo szybko pogrążyłoby się w chaosie z sprzecznymi zapisami i powszechnym zamieszaniem.
Wzorzec projektowy Singleton działa na tej samej zasadzie w twoim oprogramowaniu. Jego głównym zadaniem jest ograniczenie klasy tak, aby można było utworzyć z niej tylko jeden obiekt. Ta jedyna instancja staje się źródłem prawdy dla konkretnego zadania i jest łatwo dostępna z dowolnego miejsca w kodzie. W ten sposób wymuszasz kontrolę nad zasobami, które nigdy nie mogą być zduplikowane.
Główne przeznaczenie i analogia
Singleton to nie tylko powstrzymywanie innych przed tworzeniem nowych obiektów; chodzi o centralizację kontroli. Tak jak Królewski Pisarz zapewnia pojedynczy punkt dostępu do oficjalnych rejestrów królestwa, instancja Singleton oferuje uniwersalnie dostępne wejście do zasobu współdzielonego. Zapobiega to tworzeniu przez różne części aplikacji izolowanych i potencjalnie sprzecznych wersji.
Klasycznym przykładem jest pula połączeń do bazy danych. Z pewnością nie chcesz, żeby każdy komponent twojej aplikacji otwierał własne, oddzielne połączenie do bazy danych — to prosty sposób na wyczerpanie zasobów serwera i spowolnienie działania. Zamiast tego Singleton może zarządzać jedną pulą połączeń, wydzielając je efektywnie w razie potrzeby.
Główna idea jest prosta, ale potężna: jedna klasa, jedna instancja, jeden globalny punkt dostępu. Ta struktura gwarantuje, że wszystkie interakcje z określonym zasobem przechodzą przez pojedynczy, kontrolowany kanał.
Wzorzec Singleton w pigułce
| Charakterystyka | Opis i uzasadnienie |
|---|---|
| Pojedyncza instancja | Klasa jest zaprojektowana tak, by mieć tylko jedną instancję w całym cyklu życia aplikacji, często wymuszane prywatnym konstruktorem. |
| Globalny punkt dostępu | Statyczna metoda (np. getInstance()) zapewnia jednolity, dobrze znany sposób dostępu do instancji z dowolnego miejsca w kodzie. |
| Opóźniona inicjalizacja | Pojedyncza instancja jest często tworzona przy pierwszym żądaniu, a nie przy starcie aplikacji, co może poprawić wydajność. |
| Zarządzanie stanem | Działa jako scentralizowane miejsce dla konkretnego fragmentu globalnego stanu, na przykład ustawień aplikacji lub sesji użytkownika. |
Ta tabela zwięźle podsumowuje, dlaczego wzorzec istnieje: by wymusić pojedynczą, globalnie dostępną instancję dla zasobów, które z natury są unikalne.
Praktyczne przypadki użycia
Chociaż wzorzec Singleton ma swoich krytyków, nie brakuje mu uzasadnionych zastosowań. Najlepiej sprawdza się, gdy masz zasób, który z samej natury jest w systemie unikalny.
Oto kilka scenariuszy, gdzie Singleton ma sens:
- Usługi logowania: pojedyncza instancja loggera zapewnia, że wszystkie zdarzenia trafiają do tego samego pliku lub strumienia.
- Zarządzanie konfiguracją: jedno źródło ustawień aplikacji zapobiega niespójnościom między modułami.
- Dostęp do interfejsu sprzętowego: pojedynczy interfejs do urządzenia zapobiega konfliktom poleceń.
Zalety i wady stosowania Singletonów

Wzorzec Singleton może wydawać się niezawodnym narzędziem, gdy potrzebujesz jednego punktu dostępu do współdzielonego zasobu. Daje prosty sposób zarządzania takimi rzeczami jak obiekt konfiguracji czy usługa logowania w całej aplikacji.
Zalety Singletonów
- Globalny punkt dostępu upraszcza użycie w różnych modułach.
- Oszczędność zasobów przez opóźnioną inicjalizację może zmniejszyć koszty startu.
- Zmniejszenie duplikacji zapobiega wielu konfliktującym instancjom kosztownych zasobów.
Wady Singletonów
- Ścisłe powiązanie: klasy mogą ukrywać zależności przez sięganie do stanu globalnego.
- Stan globalny: współdzielony mutowalny stan może powodować trudne do znalezienia błędy.
- Ukryte skutki uboczne: metody polegające na Singletonie nie ujawniają tej zależności w swojej sygnaturze, co utrudnia rozumowanie i testowanie.
Wpływ na testowanie i powiązania
Singletony komplikują testy jednostkowe, ponieważ wprowadzają globalny, trwały stan. Testy ryzykują wyciek stanu między uruchomieniami, a mockowanie Singletona może być niezręczne. Nowoczesne zespoły często preferują wstrzykiwanie zależności, ponieważ czyni zależności jawymi i łatwymi do zastąpienia podczas testów.3
Równoważenie kompromisów
Decydując, czy użyć Singletona, ważne jest, by zważyć wygodę przeciw długoterminowej utrzymywalności i testowalności. W przypadku starszych baz kodu, stopniowe refaktoryzacje w kierunku wstrzykiwania zależności często są najbezpieczniejszą drogą: zachowujesz zachowanie, jednocześnie redukując ukryte powiązania i poprawiając testowalność.
Singletony wymieniają prostotę na stan globalny, więc wybieraj rozważnie, bazując na potrzebach zespołu.
Kluczowe wnioski
- Używaj Singletonów oszczędnie i tylko dla usług, które naprawdę muszą być unikalne.
- Preferuj jawne wstrzykiwanie zależności dla lepszego rozdzielenia i testowalności.
- Jeśli musisz użyć Singletona, niech będzie leniwy i miej na uwadze współbieżność oraz bezpieczeństwo wątkowe.
- W systemach legacy, stopniowo eliminuj Singletony, wprowadzając interfejsy i DI w punkcie kompozycji.
Jak zaimplementować wzorzec Singleton w TypeScript

Przejdźmy od abstrakcji do praktyki i zbudujmy nowoczesny, typowany Singleton w TypeScript. Sekret to private konstruktor i static metoda, która działa jak strażnik. To połączenie zapewnia, że żadna inna część aplikacji nie może stworzyć nowej instancji; wszyscy przechodzą przez jedyne wejście.2
Dla naszego praktycznego przykładu stworzymy ConfigManager. Ta klasa ładuje i udostępnia ustawienia aplikacji, gwarantując, że każdy komponent czyta z tego samego źródła prawdy.
Budowanie typowanego ConfigManagera
// A practical example of the Singleton pattern for configuration management.
class ConfigManager {
// 1. A private, static property to hold the single instance.
private static instance: ConfigManager;
// 2. A place to store our configuration data.
private settings: Map<string, any> = new Map();
// 3. The private constructor. This stops `new ConfigManager()` from working anywhere else.
private constructor() {
// In a real app, you'd load from a file, environment variables, or a service.
console.log("Initializing ConfigManager instance...");
this.settings.set("API_URL", "https://api.example.com");
this.settings.set("TIMEOUT", 5000);
}
// 4. The public, static method that controls access to the single instance.
public static getInstance(): ConfigManager {
if (!ConfigManager.instance) {
ConfigManager.instance = new ConfigManager();
}
return ConfigManager.instance;
}
// 5. A regular public method to get a specific setting.
public get(key: string): any {
return this.settings.get(key);
}
}
// TypeScript will stop you from doing this:
// const config = new ConfigManager(); // Error: Constructor of class 'ConfigManager' is private.
Ta struktura wykorzystuje modyfikatory dostępu TypeScript, aby wymusić klasę z pojedynczą instancją i opóźnioną inicjalizację. Wzorzec private konstruktora i static instancji jest prosty i skuteczny dla wielu prostych potrzeb.
Wykorzystanie Singletona w serwisie
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}...`);
// Real data-fetching logic would live here.
}
}
// --- Application Entry Point ---
console.log("Application starting...");
const service1 = new ApiService();
service1.fetchData();
const service2 = new ApiService();
console.log("Application finished.");
Po uruchomieniu tego kodu powinieneś zobaczyć komunikat inicjalizacji z ConfigManager tylko raz, co udowadnia, że oba serwisy otrzymały tę samą instancję.
Dlaczego Singletony mają złą reputację
Wzorzec Singleton wygląda atrakcyjnie, ponieważ jest prosty i daje globalnie dostępny obiekt. To, co może pójść źle, to ukryte powiązania, globalny mutowalny stan i koszmary związane z testowaniem. Kiedy klasa cicho sięga po instancję globalną, ukrywa zależność, która powinna być jawna w jej konstruktorze. To sprawia, że system jest trudniejszy do zrozumienia i testowania.3
Koszmary współbieżności i stan globalny
Stanowe Singletony mogą powodować warunki wyścigu w scenariuszach współbieżnych. Rozważ SessionCounter, gdzie dwa jednoczesne żądania inkrementują ten sam licznik. Bez synchronizacji oba mogą odczytać tę samą wartość początkową i zapisać sprzeczne aktualizacje. Te błędy zależą od czasu i są trudne do odtworzenia.
Dylemat testowania
Singletony czynią testy jednostkowe kruchymi, ponieważ stan może wyciekać między testami, a mockowanie staje się trudne. Testy mogą zacząć zależeć od kolejności wykonywania, a cała seria staje się niestabilna. Dlatego zespoły często przyjmują wstrzykiwanie zależności: upraszcza ono testy, bo mocki można wstrzykiwać bezpośrednio.
Mimo tych problemów Singletony wciąż występują w naturze. Często rozprzestrzeniają się w bazie kodu po ich wprowadzeniu, dlatego audyt i stopniowa refaktoryzacja są ważne przy poprawianiu architektury.
Nowoczesne alternatywy dla wzorca Singleton
Po zapoznaniu się z ryzykiem, jakie wprowadzają Singletony, prawdopodobnie pytasz: „Czego powinienem użyć zamiast tego?” Wstrzykiwanie zależności (DI) to podejście pierwszego wyboru do zarządzania zasobami współdzielonymi. DI czyni zależności jawnymi i poprawia testowalność oraz modułowość.
Wstrzykiwanie zależności kontra Singletony
Porównaj oryginalny ApiService, który sięga do Singletona, z wersją, która przyjmuje menedżera konfiguracji przez konstruktor.
interface IConfigManager {
get(key: string): any;
}
class ApiService {
private apiUrl: string;
constructor(config: IConfigManager) {
this.apiUrl = config.get("API_URL");
}
}
Teraz ApiService zależy tylko od kontraktu IConfigManager. W testach możesz przekazać fikcyjny obiekt lub mock, co sprawia, że testy są szybkie i przewidywalne.
Poprzez odwrócenie kontroli nad tym, kto tworzy zależności, komponenty stają się bardziej skoncentrowane i elastyczne. Ta idea jest kluczowa dla zasady odwrócenia zależności (Dependency Inversion Principle).
Rola kontenerów IoC
Kontener Inwersji Kontroli (IoC) zarządza tworzeniem obiektów i ich wstrzykiwaniem w aplikacji. Popularne frameworki TypeScript oferują wbudowane kontenery DI, takie jak NestJS i Angular, albo biblioteki jak InversifyJS dla ogólnych projektów.45
Kontenery pozwalają wybrać, jak obiekty są współdzielone: cykliczne (transient), o zakresie (scoped) lub o cyklu życia podobnym do singletona. Daje to korzyści jednej współdzielonej instancji bez ukrytego powiązania wynikającego z programowego Singletona.
Jak zrefaktoryzować Singletony w bazie kodu typu legacy
Działaj inkrementalnie. Zidentyfikuj, gdzie używany jest Singleton, zdefiniuj jasny interfejs opisujący jego zachowanie i zacznij zmieniać poszczególnych konsumentów, aby przyjmowali interfejs przez wstrzykiwanie w konstruktorze. Następnie podłącz konkretną instancję w punkcie kompozycji lub pozwól kontenerowi DI nią zarządzać.
Krok 1: Zidentyfikuj i izoluj Singleton
Znajdź każde wywołanie MySingleton.getInstance() i wyznacz granicę odpowiedzialności Singletona. Zdefiniuj interfejs, który wymienia publiczne metody, których potrzebujesz.
Krok 2: Wprowadź wstrzykiwanie zależności stopniowo
Refaktoryzuj jednego konsumenta na raz:
- Zmień konstruktor, aby akceptował interfejs.
- Zastąp bezpośrednie wywołania
getInstance()odwołaniami do wstrzykniętej instancji. - W punkcie tworzenia obiektów przekaż instancję Singletona, dopóki migracja nie będzie kompletna.
To utrzymuje stabilność aplikacji, podczas gdy redukujesz ukryte powiązania.
Krok 3: Zastąp Singleton zarządzaną instancją
Gdy konsumenci będą przyjmować zależności przez interfejs, możesz usunąć statyczne getInstance() i uczynić implementację zwykłą klasą z publicznym konstruktorem. Utwórz jedną instancję w punkcie kompozycji i przekaż ją tam, gdzie potrzeba, lub pozwól kontenerowi DI obsłużyć cykl życia i zakres.
Odpowiedzi na palące pytania o Singletonach
Czy Singletony zawsze są złym pomysłem?
Nie zawsze. Mogą mieć sens dla naprawdę unikalnych, bezstanowych usług, takich jak centralny logger czy adapter do sprzętu. Nawet wtedy DI i kontrolowany punkt kompozycji często oferują to samo zachowanie przy lepszej testowalności.
Jak Singletony psują testy jednostkowe?
Wprowadzają globalny, trwały stan, który może wyciekać między testami i utrudniać mockowanie. Testy mogą stać się zależne od kolejności i niestabilne. DI upraszcza testy, ponieważ mocki można wstrzykiwać bezpośrednio.
Czy klasa statyczna to w zasadzie to samo?
Nie. Klasa statyczna posiada jedynie statyczne członkowskie elementy i nie może być instancjonowana. Singleton ma jedną prawdziwą instancję i może implementować interfejsy oraz być przekazywany jako obiekt. Oba podejścia mogą prowadzić do ścisłego powiązania, więc preferuj DI dla elastyczności.
Kolejne kroki dla twojego zespołu
Zainicjuj rozmowę o tym, gdzie — jeśli w ogóle — jedna współdzielona instancja jest naprawdę wymagana. Prototypuj DI w odizolowanym module, przyjmij jasne standardy kodowania i użyj narzędzi do mierzenia długu technologicznego. Programowanie w parach i ciągła informacja zwrotna pomagają utrzymać refaktoryzacje bezpiecznymi i efektywnymi.
Pamiętaj, że żaden wzorzec projektowy nie jest uniwersalnym rozwiązaniem. Singletony mają swoje miejsce, ale muszą być używane rozważnie i w połączeniu z czystymi interfejsami oraz jasnymi regułami odpowiedzialności.
FAQ — Szybkie pytania i odpowiedzi
P: Kiedy Singleton jest odpowiedni? O: Gdy zasób jest naprawdę unikalny i bezstanowy, taki jak scentralizowany logger czy adapter do sprzętu. Preferuj DI, gdy to możliwe.
P: Jak przetestować kod, który teraz używa Singletona? O: Wprowadź interfejs, zrefaktoryzuj konsumentów, aby akceptowali interfejs, i wstrzykuj podstawkę testową. Rób to stopniowo, aby uniknąć dużych regresji.
P: Jaka jest najbezpieczniejsza ścieżka usunięcia Singletonów z aplikacji legacy? O: Zmapuj użycia, zdefiniuj interfejsy, zrefaktoryzuj konsumentów, aby przyjmowali zależności, a następnie utwórz i wstrzykuj pojedynczą instancję w punkcie kompozycji lub użyj kontenera DI.
AI pisze kod.Ty sprawiasz, że przetrwa.
W erze przyspieszenia AI czysty kod to nie tylko dobra praktyka — to różnica między systemami, które się skalują, a bazami kodu, które zapadają się pod własnym ciężarem.