Sobes.tech
Middle+

Por que o padrão Singleton é considerado um anti-padrão?

sobes.tech IA

Resposta da IA

Singleton viola os princípios SOLID e complica os testes:

  • Violação do princípio de responsabilidade única (SRP): A classe é responsável tanto pela sua lógica quanto por gerir o seu ciclo de vida (criação e acesso à instância única).
  • Violação do princípio de aberto/fechado (OCP): Expandir a funcionalidade da classe Singleton pode ser difícil sem modificar o seu código.
  • Violação do princípio de substituição de Liskov (LSP): Os subtipos da classe Singleton podem não cumprir os contratos do tipo base devido às particularidades da implementação Singleton.
  • Violação do princípio de inversão de dependências (DIP): Os módulos dependem de uma implementação concreta de Singleton, não de abstrações. Isto torna difícil substituir o objeto Singleton por um mock ou stub para testes.
  • Problemas com os testes: A instância Singleton é um estado global, o que dificulta a execução paralela de testes e o isolamento de cenários de teste. É difícil substituir as dependências do objeto Singleton.
  • Dependências ocultas: O uso de Singleton oculta as dependências entre módulos, pois elas não são passadas explicitamente.
  • Problemas de concorrência: A necessidade de sincronizar o acesso à instância Singleton em ambientes multithread pode complicar o código e gerar erros potenciais.
  • Complexidade na eliminação: A gestão do ciclo de vida do objeto Singleton e a sua eliminação podem não ser evidentes.

Em vez de Singleton, é frequentemente preferível usar injeção de dependências ou um Localizador de Serviços para gerir o ciclo de vida dos objetos e o seu acesso.

Exemplo de uma implementação típica de Singleton em Swift que demonstra os problemas mencionados:

// Singleton.swift
class DataManager {
    static let shared = DataManager() // Ponto de acesso global, oculta a dependência
    
    private init() { // Construtor privado, dificulta a criação de objetos mock
        // Inicialização de recursos ou estado
    }
    
    func fetchData() -> [String] {
        // Lógica para obter dados (por exemplo, a partir da rede ou base de dados)
        // Esta lógica está relacionada com a gestão do estado e o acesso, violando SRP
        return ["Item 1", "Item 2"]
    }
}

// Exemplo de uso
// Módulo que depende diretamente de DataManager.shared
class ViewController {
    func displayData() {
        let data = DataManager.shared.fetchData() // Dependência oculta
        print(data)
    }
}

// Os testes de ViewController tornam-se difíceis:
// Como substituir DataManager.shared por um objeto mock?
// Como isolar os testes se DataManager.shared é global?