ti-enxame.com

Quando o Singleton é apropriado?

Alguns sustentam que o Padrão Singleton é sempre um antipadrão. O que você acha?

67
Fishtoaster

As duas principais críticas a Singletons se enquadram em dois campos, pelo que observei:

  1. Singletons são mal utilizados e abusados ​​por programadores menos capazes e, portanto, tudo se torna um singleton e você vê o código repleto de referências Class :: get_instance (). De um modo geral, existem apenas um ou dois recursos (como uma conexão com o banco de dados, por exemplo) que se qualificam para o uso do padrão Singleton.
  2. Singletons são essencialmente classes estáticas, contando com um ou mais métodos e propriedades estáticos. Todas as coisas estáticas apresentam problemas reais e tangíveis quando você tenta fazer o Teste de Unidade, porque eles representam becos sem saída no seu código que não podem ser zombados ou stub. Como resultado, ao testar uma classe que depende de um Singleton (ou qualquer outro método ou classe estática), você não está apenas testando essa classe, mas também o método ou a classe estática.

Como resultado de ambos, uma abordagem comum é usar criar um objeto de contêiner amplo para armazenar uma única instância dessas classes e apenas o objeto de contêiner modifica esses tipos de classes, enquanto muitas outras classes podem ter acesso a elas para usar a partir de o objeto contêiner.

32
Noah Goodrich

Eu concordo que é um anti-padrão. Por quê? Porque ele permite que o seu código se detenha sobre suas dependências, e você não pode confiar que outros programadores não introduzam um estado mutável nos seus singletons anteriormente imutáveis.

Uma classe pode ter um construtor que usa apenas uma sequência, portanto, você acha que é instanciada isoladamente e não tem efeitos colaterais. No entanto, silenciosamente, ele está se comunicando com algum tipo de objeto singleton público disponível globalmente, de modo que sempre que você instancia a classe, ela contém dados diferentes. Este é um grande problema, não apenas para usuários da sua API, mas também para a testabilidade do código. Para testar o código da unidade corretamente, você precisa gerenciar e estar ciente do estado global no singleton para obter resultados consistentes.

42
Magnus Wolffelt

O padrão Singleton é basicamente apenas uma variável global inicializada preguiçosamente. As variáveis ​​globais são geralmente e corretamente consideradas más porque permitem ações assustadoras à distância entre partes aparentemente não relacionadas de um programa. No entanto, IMHO não há nada errado com variáveis ​​globais que são definidas uma vez, de um lugar, como parte da rotina de inicialização de um programa (por exemplo, lendo um arquivo de configuração ou argumentos da linha de comando) e tratadas como constantes a partir de então. Esse uso de variáveis ​​globais é diferente apenas em letra, não em espírito, de ter uma constante nomeada declarada em tempo de compilação.

Da mesma forma, minha opinião sobre os Singletons é que eles são ruins se, e somente se, são usados ​​para passar um estado mutável entre partes aparentemente não relacionadas de um programa. Se eles não contiverem estado mutável, ou se o estado mutável que eles contêm for completamente encapsulado, para que os usuários do objeto não precisem saber disso, mesmo em um ambiente multithread, não há nada de errado com eles.

34
dsimcha

Por que as pessoas o usam?

Eu já vi vários singletons no mundo PHP. Não me lembro de nenhum caso de uso em que achei o padrão justificado. Mas acho que tive uma idéia sobre a motivação por que as pessoas o usaram.

  1. Único instância.

    "Use uma única instância da classe C em todo o aplicativo."

    Este é um requisito razoável, p. para a "conexão de banco de dados padrão". Isso não significa que você nunca criará uma segunda conexão db, apenas significa que geralmente trabalha com a conexão padrão.

  2. Único instanciação.

    "Não permita que a classe C seja instanciada mais de uma vez (por processo, por solicitação, etc)."

    Isso é relevante apenas se a instanciação da classe tiver efeitos colaterais conflitantes com outras instâncias.

    Muitas vezes, esses conflitos podem ser evitados com a reformulação do componente - por exemplo, eliminando efeitos colaterais do construtor de classe. Ou eles podem ser resolvidos de outras maneiras. Mas ainda pode haver alguns casos de uso legítimos.

    Você também deve pensar se o requisito "único" realmente significa "um por processo". Por exemplo. para simultaneidade de recursos, o requisito é "um por sistema inteiro, entre processos" e não "um por processo". E para outras coisas, é mais por "contexto de aplicativo", e você tem um contexto de aplicativo por processo.

    Caso contrário, não há necessidade de impor essa suposição. A imposição disso também significa que você não pode criar uma instância separada para testes de unidade.

  3. Acesso global.

    Isso é legítimo apenas se você não tiver uma infraestrutura adequada para passar objetos para o local onde eles são usados. Isso pode significar que sua estrutura ou ambiente é péssimo, mas pode não estar ao seu alcance corrigir isso.

    O preço é forte, dependências ocultas e tudo o que é ruim no estado global. Mas você provavelmente já está sofrendo isso.

  4. Instanciação lenta

    Esta não é uma parte necessária de um singleton, mas parece a maneira mais popular de implementá-los. Mas, embora a instanciação preguiçosa seja uma coisa agradável de se ter, você realmente não precisa de um singleton para conseguir isso.

Implementação típica

A implementação típica é uma classe com um construtor privado, uma variável de instância estática e um método estático getInstance () com instanciação lenta.

Além dos problemas mencionados acima, isso ocorre com o princípio de responsabilidade única , porque a classe faz controla sua própria instanciação e ciclo de vida , além das outras responsabilidades que o classe já tem.

Conclusão

Em muitos casos, você pode obter o mesmo resultado sem um singleton e sem estado global. Em vez disso, você deve usar a injeção de dependência e pode considerar um contêiner de injeção de dependência .

No entanto, existem casos de uso em que você tem os seguintes requisitos válidos restantes:

  • Instância única (mas não instanciação única)
  • Acesso global (porque você está trabalhando com uma estrutura da idade do bronze)
  • Instanciação preguiçosa (porque é bom ter)

Então, aqui está o que você pode fazer neste caso:

  • Crie uma classe C que você deseja instanciar, com um construtor público.

  • Crie uma classe S separada com uma variável de instância estática e um método estático S :: getInstance () com instanciação lenta, que usará a classe C para a instância.

  • Elimine todos os efeitos colaterais do construtor C. Em vez disso, coloque esses efeitos colaterais no método S :: getInstance ().

  • Se você tiver mais de uma classe com os requisitos acima, considere gerenciar as instâncias de classe com um contêiner de serviço local pequeno e use a instância estática apenas para o contêiner. Portanto, S :: getContainer () fornecerá um contêiner de serviço lento e instanciado, e você obterá os outros objetos do contêiner.

  • Evite chamar o getInstance () estático onde puder. Use injeção de dependência, sempre que possível. Especialmente, se você usar a abordagem de contêiner com vários objetos que dependem um do outro, nenhum deles deverá chamar S :: getContainer ().

  • Opcionalmente, crie uma interface que a classe C implemente e use-a para documentar o valor de retorno de S :: getInstance ().

(Ainda chamamos isso de singleton? Deixo isso na seção de comentários ..)

Benefícios:

  • Você pode criar uma instância separada de C para teste de unidade, sem tocar em nenhum estado global.

  • O gerenciamento de instância é separado da própria classe -> separação de preocupações, princípio de responsabilidade única.

  • Seria muito fácil deixar S :: getInstance () usar uma classe diferente para a instância ou até mesmo determinar dinamicamente qual classe usar.

7
donquixote

Pessoalmente, usarei singletons quando precisar de 1, 2 ou 3 ou uma quantidade limitada de objetos para a classe em questão. Ou quero transmitir ao usuário da minha classe que não quero que várias instâncias da minha classe sejam criadas para que funcione corretamente.

Além disso, eu o usarei apenas quando precisar usá-lo em quase todos os lugares do meu código e não desejar passar um objeto como parâmetro para cada classe ou função que precisar.

Além disso, usarei apenas um singleton se ele não quebrar a transparência referencial de outra função. Significado dado alguma entrada, sempre produzirá a mesma saída. I.e. Não o uso para o estado global. A menos que, possivelmente, esse estado global seja inicializado uma vez e nunca seja alterado.

Quanto a quando não usá-lo, veja os 3 acima e altere-os para o oposto.

1
Brian R. Bondy