Sobes.tech
Middle+

Warum gilt das Singleton-Muster als Anti-Pattern?

sobes.tech KI

Antwort von AI

Singleton verletzt die SOLID-Prinzipien und erschwert das Testen:

  • Verstoß gegen das Prinzip der einzigen Verantwortung (SRP): Die Klasse ist sowohl für ihre Logik als auch für die Verwaltung ihres Lebenszyklus (Erstellung und Zugriff auf die einzelne Instanz) verantwortlich.
  • Verstoß gegen das Prinzip der Offenheit/Schließung (OCP): Die Erweiterung der Funktionalität der Singleton-Klasse kann schwierig sein, ohne ihren Code zu ändern.
  • Verstoß gegen das Liskov-Substitutionsprinzip (LSP): Untertypen der Singleton-Klasse erfüllen möglicherweise nicht die Verträge des Basistyps aufgrund der Besonderheiten der Singleton-Implementierung.
  • Verstoß gegen das Prinzip der Dependency Inversion (DIP): Module hängen von einer konkreten Implementierung des Singletons ab, nicht von Abstraktionen. Das macht es schwierig, das Singleton-Objekt für Tests durch ein Mock oder Stub zu ersetzen.
  • Probleme beim Testen: Die Singleton-Instanz ist ein globaler Zustand, was parallele Tests und die Isolierung von Testszenarien erschwert. Es ist schwierig, Abhängigkeiten des Singleton-Objekts zu ersetzen.
  • Versteckte Abhängigkeiten: Die Verwendung von Singleton verbirgt die Abhängigkeiten zwischen Modulen, da sie nicht explizit übergeben werden.
  • Probleme mit der Multithread-Sicherheit: Die Notwendigkeit, den Zugriff auf die Singleton-Instanz in einer Multithread-Umgebung zu synchronisieren, kann den Code verkomplizieren und zu Fehlern führen.
  • Komplexität bei der Entsorgung: Die Verwaltung des Lebenszyklus des Singleton-Objekts und seine Entsorgung können nicht offensichtlich sein.

Statt Singleton ist es oft vorzuziehen, Dependency Injection oder Service Locator zu verwenden, um den Lebenszyklus der Objekte und deren Zugriff zu verwalten.

Beispiel für eine typische Singleton-Implementierung in Swift, die die genannten Probleme demonstriert:

// Singleton.swift
class DataManager {
    static let shared = DataManager() // Globaler Zugriffspunkt, verbirgt die Abhängigkeit
    
    private init() { // Privater Konstruktor, erschwert das Erstellen von Mock-Objekten
        // Initialisierung von Ressourcen oder Zustand
    }
    
    func fetchData() -> [String] {
        // Logik zum Abrufen von Daten (z.B. aus Netzwerk oder Datenbank)
        // Diese Logik ist mit der Verwaltung des Zustands und dem Zugriff verbunden, verletzt SRP
        return ["Item 1", "Item 2"]
    }
}

// Beispiel für die Verwendung
// Modul, das direkt von DataManager.shared abhängt
class ViewController {
    func displayData() {
        let data = DataManager.shared.fetchData() // Verborgene Abhängigkeit
        print(data)
    }
}

// Das Testen von ViewController wird schwierig:
// Wie kann DataManager.shared durch ein Mock-Objekt ersetzt werden?
// Wie kann man Tests isolieren, wenn DataManager.shared global ist?