Quando o Singleton é apropriado? [fechadas]

Respostas:

32

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 a 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 elas 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.

Noah Goodrich
fonte
6
Gostaria apenas de dizer que você pode zombar de métodos estáticos em Java com o JMockit ( code.google.com/p/jmockit ). É uma ferramenta muito útil.
Michael K
3
Algumas reflexões: o contêiner é um Singleton, então você não se livrou dos Singletons. Além disso, se você estiver escrevendo uma biblioteca com uma API pública, não poderá forçar o usuário da sua API a usar seu contêiner. Se você precisar de um gerenciador de recursos global em sua biblioteca (protegendo o acesso a um único recurso físico, por exemplo), precisará usar um Singleton.
Scott Whitlock
2
@ Scott Whitlock, por que precisa ser um singleton? Só porque existe apenas uma instância, não é assim. Qualquer biblioteca IoC irá gerir este tipo de dependência ...
MattDavey
Esta solução proposta é também conhecido como Toolbox
cregox
41

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

Uma classe pode ter um construtor que usa apenas uma sequência, então 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. Esse é 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.

Magnus Wolffelt
fonte
Gostaria de poder votar mais de uma vez!
MattDavey
34

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 de 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.

dsimcha
fonte
3
Portanto, em outras palavras, você é contra qualquer uso de um banco de dados em um programa.
Kendall Helmstetter Gelner
Há uma famosa google talk tecnologia de vídeo que ecoa a sua opinião youtube.com/watch?v=-FRm3VPhseI
Martin Iorque
2
@KendallHelmstetterGelner: Há uma diferença entre o estado e o armazenamento secundário. É improvável que você mantenha um banco de dados inteiro na memória.
Martin York
7

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 pela qual as pessoas a usaram.

  1. Instância única .

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

    Este é um requisito razoável, por exemplo, 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. Instanciação única .

    "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 que conflitam com outras instâncias.

    Frequentemente, esses conflitos podem ser evitados através da reformulação do componente - por exemplo, eliminando os 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 preguiçosa.

    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 se aplica ao princípio da responsabilidade única , porque a classe controla sua própria instanciação e ciclo de vida , além das outras responsabilidades que a classe já possui.

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 pequeno contêiner de serviço local e use a instância estática apenas para o contêiner. Portanto, S :: getContainer () fornecerá a você 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âncias é 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.

Don Quixote
fonte
1

Pessoalmente, usarei singletons quando precisar de 1, 2 ou 3 ou uma quantidade limitada de objetos para a classe em questão. Ou desejo 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 somente 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 que sempre produzirá a mesma saída. Ou seja, eu não o uso para o estado global. A menos que 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 contrário.

Brian R. Bondy
fonte
3
-0 mas eu quase -1. Eu preferiria passá-lo em todo lugar no meu código, em vez de usar um singleton, mas se eu for realmente preguiçoso, posso fazer isso. Em vez disso, usaria vars globais se eu puder ou membros estáticos. vars globais são realmente muito preferíveis (para mim) a singletons. Globals não mentem