Alguns sustentam que o Padrão Singleton é sempre um antipadrão. O que você acha?
Alguns sustentam que o Padrão Singleton é sempre um antipadrão. O que você acha?
Respostas:
As duas principais críticas a Singletons se enquadram em dois campos, pelo que observei:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
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.
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.