Singletons são como o comunismo: ambos parecem ótimos no papel, mas explodem em problemas na prática.
O padrão singleton coloca uma ênfase desproporcional na facilidade de acesso aos objetos. Ele evita completamente o contexto, exigindo que cada consumidor use um objeto com escopo AppDomain, sem deixar opções para implementações variadas. Ele incorpora conhecimento de infraestrutura em suas aulas (a chamada para GetInstance()
) enquanto adiciona potência expressiva exatamente zero . Na verdade, diminui seu poder expressivo, porque você não pode alterar a implementação usada por uma classe sem alterá-la para todas elas. Você simplesmente não pode adicionar peças únicas de funcionalidade.
Além disso, quando a classe Foo
depende de Logger.GetInstance()
, Foo
está efetivamente ocultando suas dependências dos consumidores. Isso significa que você não pode entendê Foo
-lo totalmente ou usá-lo com confiança, a menos que leia sua fonte e descubra o fato de que depende Logger
. Se você não tem o código-fonte, isso limita quão bem você pode entender e usar efetivamente o código do qual depende.
O padrão singleton, conforme implementado com propriedades / métodos estáticos, é pouco mais do que um hack em torno da implementação de uma infraestrutura. Isso o limita de inúmeras maneiras, ao mesmo tempo que não oferece nenhum benefício perceptível sobre as alternativas. Você pode usá-lo como quiser, mas como existem alternativas viáveis que promovem um design melhor, nunca deve ser uma prática recomendada.
Outros explicaram muito bem o problema com os singletons em geral. Gostaria apenas de acrescentar uma observação sobre o caso específico do Logger. Concordo com você que geralmente não é um problema acessar um Logger (ou o logger root, para ser mais preciso) como um singleton, por meio de um método estático
getInstance()
ougetRootLogger()
. (a menos que você queira ver o que é registrado pela classe que você está testando - mas, em minha experiência, dificilmente me lembro desses casos em que isso foi necessário. Novamente, para outros, essa pode ser uma preocupação mais urgente).IMO geralmente um registrador de singleton não é uma preocupação, pois não contém nenhum estado relevante para a classe que você está testando. Ou seja, o estado do logger (e suas possíveis mudanças) não têm nenhum efeito sobre o estado da classe testada. Portanto, isso não torna seus testes de unidade mais difíceis.
A alternativa seria injetar o logger por meio do construtor em (quase) todas as classes em seu aplicativo. Para consistência de interfaces, deve ser injetado mesmo se a classe em questão não registrar nada no momento - a alternativa seria que quando você descobrir em algum ponto que agora você precisa registrar algo desta classe, você precisa de um logger, portanto você precisa adicionar um parâmetro de construtor para DI, quebrando todo o código do cliente. Não gosto de ambas as opções e sinto que usar DI para registro seria apenas complicar minha vida para cumprir uma regra teórica, sem nenhum benefício concreto.
Portanto, meu ponto principal é: uma classe que é usada (quase) universalmente, mas não contém o estado relevante para seu aplicativo, pode ser implementada com segurança como Singleton .
fonte
É principalmente, mas não totalmente, sobre testes. Os solteiros eram populares porque eram fáceis de consumir, mas há uma série de desvantagens para os solteiros.
O DI fornece o consumo fácil de suas classes dependentes - basta colocá-lo nos args do construtor e o sistema fornece isso para você - enquanto fornece a flexibilidade de teste e construção.
fonte
Praticamente a única vez em que você deve usar um Singleton em vez de Dependency Injection é se o Singleton representa um valor imutável, como List.Empty ou semelhante (assumindo listas imutáveis).
A verificação do intestino para um Singleton deve ser "eu estaria bem se esta fosse uma variável global em vez de um Singleton?" Caso contrário, você está usando o padrão Singleton para encobrir uma variável global e deve considerar uma abordagem diferente.
fonte
Acabei de verificar o artigo Monostate - é uma alternativa bacana ao Singleton, mas tem algumas propriedades estranhas:
Isso não é assustador - porque o mapeador é realmente dependente da conexão do banco de dados para executar save () - mas se outro mapeador foi criado antes - ele pode pular esta etapa na aquisição de suas dependências. Embora seja legal, também é meio bagunçado, não é?
fonte
Existem outras alternativas para Singleton: padrões Proxy e MonoState.
http://www.objectmentor.com/resources/articles/SingletonAndMonostate.pdf
Como o padrão de proxy pode ser usado para substituir um singleton?
fonte