Middle+
Dlaczego wzorzec Singleton jest uważany za antywzorzec?
sobes.tech AI
Odpowiedź od AI
Singleton narusza zasady SOLID i utrudnia testowanie:
- Naruszenie zasady pojedynczej odpowiedzialności (SRP): Klasa odpowiada zarówno za własną logikę, jak i za zarządzanie swoim cyklem życia (tworzenie i dostęp do pojedynczego egzemplarza).
- Naruszenie zasady otwartości/zamknięcia (OCP): Rozszerzenie funkcjonalności klasy Singleton może być trudne bez zmiany jej kodu.
- Naruszenie zasady podstawiania Liskov (LSP): Podtypy klasy Singleton mogą nie spełniać kontraktów typu bazowego z powodu specyfiki implementacji Singleton.
- Naruszenie zasady odwrócenia zależności (DIP): Moduły zależą od konkretnej implementacji Singleton, a nie od abstrakcji. To utrudnia podmianę obiektu Singleton na mock lub stub podczas testów.
- Problemy z testowaniem: Instancja Singleton jest stanem globalnym, co utrudnia równoległe wykonywanie testów i izolację scenariuszy testowych. Trudno podmienić zależności obiektu Singleton.
- Ukryte zależności: Użycie Singleton ukrywa zależności między modułami, ponieważ nie są one przekazywane jawnie.
- Problemy z wielowątkowością: Konieczność synchronizacji dostępu do instancji Singleton w środowisku wielowątkowym może prowadzić do komplikacji i potencjalnych błędów.
- Złożoność utylizacji: Zarządzanie cyklem życia obiektu Singleton i jego utylizacja mogą być nieoczywiste.
Zamiast Singleton często lepiej jest używać wstrzykiwania zależności (Dependency Injection) lub Service Locator do zarządzania cyklem życia obiektów i ich dostępem.
Przykład typowej implementacji Singleton w Swift, który demonstruje wymienione problemy:
// Singleton.swift
class DataManager {
static let shared = DataManager() // Globalny punkt dostępu, ukrywa zależność
private init() { // Prywatny konstruktor, utrudnia tworzenie mock-ów
// Inicjalizacja zasobów lub stanu
}
func fetchData() -> [String] {
// Logika pobierania danych (np. z sieci lub bazy danych)
// Ta logika jest związana z zarządzaniem stanem i dostępem, narusza SRP
return ["Item 1", "Item 2"]
}
}
// Przykład użycia
// Moduł, który bezpośrednio zależy od DataManager.shared
class ViewController {
func displayData() {
let data = DataManager.shared.fetchData() // Ukryta zależność
print(data)
}
}
// Testowanie ViewController staje się trudne:
// Jak podmienić DataManager.shared na mock?
// Jak izolować testy, jeśli DataManager.shared jest globalny?