Sobes.tech
Middle+

Why is the Singleton pattern considered an anti-pattern?

sobes.tech AI

Answer from AI

Singleton violates SOLID principles and complicates testing:

  • Single Responsibility Principle (SRP) violation: The class is responsible both for its logic and for managing its lifecycle (creating and accessing the single instance).
  • Open/Closed Principle (OCP) violation: Extending the functionality of a Singleton class can be difficult without modifying its code.
  • Liskov Substitution Principle (LSP) violation: Subtypes of the Singleton class may not satisfy the contracts of the base type due to implementation specifics.
  • Dependency Inversion Principle (DIP) violation: Modules depend on the concrete implementation of the Singleton rather than abstractions. This makes it difficult to replace the Singleton object with a mock or stub for testing.
  • Testing issues: The Singleton instance is a global state, which complicates parallel test execution and test scenario isolation. Dependencies on the Singleton object are hard to substitute.
  • Hidden dependencies: Using Singleton hides dependencies between modules, as they are not explicitly passed.
  • Multithreading issues: Synchronizing access to the Singleton instance in a multithreaded environment can lead to code complexity and potential errors.
  • Utilization complexity: Managing the lifecycle and disposal of the Singleton object can be non-obvious.

Instead of Singleton, dependency injection or a service locator is often preferred for managing object lifecycles and access.

A typical implementation of Singleton in Swift demonstrating these issues:

// Singleton.swift
class DataManager {
    static let shared = DataManager() // Global access point, hides dependency
    
    private init() { // Private initializer, complicates creating mock objects
        // Initialization of resources or state
    }
    
    func fetchData() -> [String] {
        // Logic for fetching data (e.g., from network or database)
        // This logic is tied to state management and access, violating SRP
        return ["Item 1", "Item 2"]
    }
}

// Usage example
// Module that directly depends on DataManager.shared
class ViewController {
    func displayData() {
        let data = DataManager.shared.fetchData() // Hidden dependency
        print(data)
    }
}

// Testing ViewController becomes difficult:
// How to replace DataManager.shared with a mock object?
// How to isolate tests if DataManager.shared is a global?