Ultimamente, tem havido muita discussão sobre os problemas com o uso (e o uso excessivo) de Singletons. Também fui uma dessas pessoas no início da minha carreira. Posso ver qual é o problema agora e, no entanto, ainda existem muitos casos em que não vejo uma boa alternativa - e poucas discussões anti-Singleton realmente fornecem uma.
Aqui está um exemplo real de um grande projeto recente em que estive envolvido:
O aplicativo era um cliente espesso com muitas telas e componentes separados, que usam grandes quantidades de dados de um estado de servidor que não é atualizado com muita frequência. Esses dados foram basicamente armazenados em cache em um objeto "gerenciador" de Singleton - o temido "estado global". A idéia era ter esse lugar no aplicativo que mantém os dados armazenados e sincronizados e, em seguida, qualquer nova tela aberta pode apenas consultar a maior parte do que precisa a partir daí, sem fazer pedidos repetidos de vários dados de suporte do servidor. Solicitar constantemente ao servidor exigiria muita largura de banda - e eu estou falando milhares de dólares em contas extras da Internet por semana, o que era inaceitável.
Existe alguma outra abordagem que possa ser apropriada aqui, além de basicamente ter esse tipo de objeto de cache do gerenciador de dados global? Esse objeto não precisa oficialmente ser um "Singleton", é claro, mas conceitualmente faz sentido ser um. O que é uma boa alternativa limpa aqui?
fonte
Respostas:
É importante distinguir aqui entre instâncias únicas e o padrão de design Singleton .
Instâncias únicas são simplesmente uma realidade. A maioria dos aplicativos é projetada para funcionar apenas com uma configuração por vez, uma interface do usuário por vez, um sistema de arquivos por vez e assim por diante. Se houver muitos estados ou dados a serem mantidos, certamente você desejaria ter apenas uma instância e mantê-los ativos o maior tempo possível.
O padrão de design Singleton é um tipo muito específico de instância única, especificamente uma que é:
É por causa dessa escolha de design específico que o padrão apresenta vários problemas em potencial a longo prazo:
Na verdade, nenhum desses sintomas é endêmico para instâncias únicas, apenas o padrão Singleton.
O que você pode fazer? Simplesmente não use o padrão Singleton.
Citando a pergunta:
Esse conceito tem um nome, como você sugere, mas parece incerto. É chamado de cache . Se você quiser se divertir, pode chamá-lo de "cache offline" ou apenas uma cópia offline de dados remotos.
Um cache não precisa ser um singleton. Pode ser necessário que seja uma única instância se você deseja evitar a busca dos mesmos dados para várias instâncias de cache; mas isso não significa que você realmente exponha tudo a todos .
A primeira coisa que eu faria é separar as diferentes áreas funcionais do cache em interfaces separadas. Por exemplo, digamos que você estivesse criando o pior clone do YouTube do mundo com base no Microsoft Access:
Aqui você tem várias interfaces que descrevem os tipos específicos de dados aos quais uma determinada classe pode precisar acessar - mídia, perfis de usuário e páginas estáticas (como a primeira página). Tudo isso é implementado por um mega cache, mas você projeta suas classes individuais para aceitar as interfaces, para que elas não se importem com o tipo de instância que possuem. Você inicializa a instância física uma vez, quando o programa é iniciado e, em seguida, começa a passar pelas instâncias (convertidas para um tipo de interface específico) por meio de construtores e propriedades públicas.
Isso é chamado de injeção de dependência , a propósito; você não precisa usar o Spring ou qualquer contêiner IoC especial, desde que o design de sua classe geral aceite suas dependências do chamador, em vez de instanciá-las por conta própria ou fazer referência ao estado global .
Por que você deve usar o design baseado em interface? Três razões:
Facilita a leitura do código; você pode entender claramente pelas interfaces exatamente de quais dados as classes dependentes dependem.
Se e quando você perceber que o Microsoft Access não era a melhor opção para um back-end de dados, poderá substituí-lo por algo melhor - digamos, SQL Server.
Se e quando você perceber que o SQL Server não é a melhor opção especificamente para a mídia , poderá interromper sua implementação sem afetar nenhuma outra parte do sistema . É aí que entra o verdadeiro poder da abstração.
Se você quiser dar um passo adiante, use um contêiner IoC (estrutura DI) como Spring (Java) ou Unity (.NET). Quase toda estrutura de DI fará seu próprio gerenciamento vitalício e permitirá especificamente que você defina um serviço específico como uma instância única (geralmente denominando "singleton", mas isso é apenas para familiaridade). Basicamente, essas estruturas economizam a maior parte do trabalho do macaco de passar manualmente as instâncias, mas elas não são estritamente necessárias. Você não precisa de nenhuma ferramenta especial para implementar esse design.
Por uma questão de integridade, devo salientar que o design acima também não é ideal. Ao lidar com um cache (como você é), você deve realmente ter uma camada totalmente separada . Em outras palavras, um design como este:
O benefício disso é que você nunca precisará interromper sua
Cache
instância se decidir refatorar; você pode alterar como a Mídia é armazenada simplesmente alimentando uma implementação alternativa deIMediaRepository
. Se você pensar em como isso se encaixa, verá que ele ainda cria apenas uma instância física de um cache, para que você nunca precise buscar os mesmos dados duas vezes.Nada disso significa dizer que todo software do mundo precisa ser arquitetado para esses padrões exigentes de alta coesão e acoplamento flexível; depende do tamanho e escopo do projeto, sua equipe, seu orçamento, prazos etc. Mas se você está perguntando qual é o melhor design (para usar no lugar de um singleton), então é isso.
PS Como outros já declararam, provavelmente não é a melhor idéia para as classes dependentes estarem cientes de que estão usando um cache - que é um detalhe de implementação com o qual simplesmente nunca deveriam se preocupar. Dito isto, a arquitetura geral ainda seria muito parecida com a mostrada acima, você simplesmente não se referiria às interfaces individuais como Caches . Em vez disso, você os chamaria Serviços ou algo semelhante.
fonte
No caso de você indicar, parece que o uso de um Singleton não é o problema, mas o sintoma de um problema - um problema arquitetônico maior.
Por que as telas estão consultando o objeto de cache em busca de dados? O armazenamento em cache deve ser transparente para o cliente. Deve haver uma abstração apropriada para fornecer os dados, e a implementação dessa abstração pode utilizar armazenamento em cache.
O problema é provável que as dependências entre partes do sistema não estejam configuradas corretamente, e isso provavelmente é sistêmico.
Por que as telas precisam ter conhecimento de onde obtêm seus dados? Por que as telas não recebem um objeto que pode atender às solicitações de dados (atrás das quais um cache está oculto)? Muitas vezes, a responsabilidade pela criação de telas não é centralizada e, portanto, não há um ponto claro de injetar as dependências.
Mais uma vez, estamos analisando questões de arquitetura e design em larga escala.
Além disso, é muito importante entender que a vida útil de um objeto pode ser completamente separada da maneira como o objeto é encontrado para uso.
Um cache terá que permanecer durante todo o tempo de vida do aplicativo (para ser útil), para que o tempo de vida do objeto seja o de um Singleton.
Mas o problema com o Singleton (pelo menos a implementação comum do Singleton como uma classe / propriedade estática), é como as outras classes que o usam procuram encontrá-lo.
Com uma implementação estática de Singleton, a convenção é simplesmente usá-la sempre que necessário. Mas isso esconde completamente a dependência e une firmemente as duas classes.
Se fornecermos a dependência para a classe, essa dependência é explícita e toda a classe consumidora precisa ter conhecimento do contrato disponível para uso.
fonte
Escrevi um capítulo inteiro sobre essa questão. Principalmente no contexto de jogos, mas a maioria deve se aplicar fora dos jogos.
tl; dr:
O padrão Gang of Four Singleton faz duas coisas: fornece acesso conveniente a um objeto de qualquer lugar e garante que apenas uma instância dele possa ser criada. Em 99% do tempo, tudo o que importa é a primeira metade disso, e carregar ao longo da segunda metade para obtê-lo adiciona uma limitação desnecessária.
Não apenas isso, mas existem soluções melhores para oferecer acesso conveniente. Tornar um objeto global é a opção nuclear para resolver isso e facilita a destruição do seu encapsulamento. Tudo o que é ruim nos globais se aplica completamente aos singletons.
Se você o estiver usando apenas porque possui muitos lugares no código que precisam tocar no mesmo objeto, tente encontrar uma maneira melhor de fornecê-lo apenas a esses objetos sem expô-lo a toda a base de código. Outras soluções:
Abandone completamente. Eu já vi muitas classes singleton que não têm nenhum estado e são apenas sacos de funções auxiliares. Aqueles não precisam de uma instância. Apenas torne-as funções estáticas ou mova-as para uma das classes que a função usa como argumento. Você não precisaria de uma
Math
aula especial se pudesse123.Abs()
.Passe por aí. A solução simples, se um método precisa de algum outro objeto, é apenas passá-lo. Não há nada errado em passar alguns objetos.
Coloque-o na classe base. Se você tem muitas classes que precisam acessar algum objeto especial e elas compartilham uma classe base, você pode tornar esse objeto um membro na base. Ao construí-lo, passe o objeto. Agora, todos os objetos derivados podem obtê-lo quando necessário. Se você o proteger, garante que o objeto ainda fique encapsulado.
fonte
Não é o estado global em si que é o problema.
Realmente você só precisa se preocupar
global mutable state
. O estado constante não é afetado pelos efeitos colaterais e, portanto, é menos problemático.A principal preocupação com o singleton é que ele adiciona acoplamento e, portanto, dificulta o teste. Você pode reduzir o acoplamento obtendo o singleton de outra fonte (por exemplo, uma fábrica). Isso permitirá que você desacople o código de uma instância específica (embora você fique mais acoplado à fábrica (mas pelo menos a fábrica possa ter implementações alternativas para diferentes fases)).
Na sua situação, acho que você pode se safar desde que seu singleton realmente implemente uma interface (para que uma alternativa possa ser usada em outras situações).
Mas outra grande desvantagem dos singletons é que, uma vez instalados, removê-los do código e substituí-los por outra coisa torna-se uma tarefa muito difícil (existe esse acoplamento novamente).
fonte
le weekend
oops, é uma das nossas). Obrigado :-)Então o que? Como ninguém disse isso: Caixa de ferramentas . Ou seja, se você deseja variáveis globais .
Mas adivinhem? É um singleton!
E o que é um singleton?
Talvez seja aí que a confusão começa.
Para mim, o singleton é um objeto imposto para ter apenas uma instância e sempre. Você pode acessá-lo em qualquer lugar, a qualquer momento, sem a necessidade de instanciar. É por isso que está tão intimamente relacionado
static
. Para comparação,static
é basicamente a mesma coisa, exceto que não é uma instância. Não precisamos instanciar, nem podemos, porque é alocado automaticamente. E isso pode e traz problemas.Pela minha experiência, a simples substituição
static
de Singleton resolveu muitos problemas em um projeto de bolsa de retalhos de tamanho médio em que estou participando. Isso significa apenas que ele tem algum uso para projetos mal projetados. Acho que há muita discussão se o padrão singleton é útil ou não e não posso argumentar se é realmente ruim . Mas ainda existem bons argumentos a favor do singleton sobre métodos estáticos, em geral .A única coisa que tenho certeza de que é ruim nos singletons é quando os usamos, ignorando as boas práticas. Isso é realmente algo não tão fácil de lidar. Mas más práticas podem ser aplicadas a qualquer padrão. E, eu sei, é genérico demais para dizer isso ... quero dizer, é demais.
Não me interpretem mal!
Simplificando, assim como os vars globais , os singletons ainda devem ser evitados o tempo todo . Especialmente porque eles são abusados demais. Mas os vars globais nem sempre podem ser evitados e devemos usá-los nesse último caso.
Enfim, existem muitas outras sugestões além da Caixa de ferramentas e, assim como a caixa de ferramentas, cada uma tem sua aplicação ...
Outras alternativas
O melhor artigo que eu acabei de ler sobre singletons sugere o Service Locator como uma alternativa. Para mim, isso é basicamente uma " Caixa de ferramentas estática ", se você preferir. Em outras palavras, torne o Localizador de Serviço um Singleton e você terá uma Caixa de Ferramentas. Isso contraria sua sugestão inicial de evitar o singleton, é claro, mas isso é apenas para reforçar a questão do singleton, como ele é usado, não o padrão em si.
Outros sugerem o padrão de fábrica como uma alternativa. Foi a primeira alternativa que ouvi de um colega e a eliminamos rapidamente para nosso uso como var global . Com certeza tem seu uso, mas o mesmo acontece com os singletons.
Ambas as alternativas acima são boas alternativas. Mas tudo depende do seu uso.
Agora, implicar singletons deve ser evitado a todo custo é apenas errado ...
As incapacidades (para abstrair ou subclasse) estão realmente presentes, mas e daí? Não é para isso. Não há impossibilidade de interface , pelo que sei . O acoplamento alto também pode estar presente, mas isso é apenas porque é usado com frequência. Não precisa . De fato, o acoplamento em si não tem nada a ver com o padrão singleton. Sendo esclarecido, também já elimina a dificuldade de testar. Quanto à dificuldade de paralelizar, isso depende da linguagem e da plataforma, portanto, novamente, não é um problema no padrão.
Exemplos práticos
Muitas vezes vejo 2 sendo usados, a favor e contra singletons. Cache da Web (meu caso) e serviço de log .
O registro, alguns argumentam , é um exemplo singleton perfeito, porque, e cito:
Enquanto outros argumentam, fica difícil expandir o serviço de log quando você perceber que ele não deve ser apenas uma instância.
Bem, eu digo que ambos os argumentos são válidos. O problema aqui, novamente, não está no padrão singleton. É nas decisões de arquitetura e ponderação se a refatoração é um risco viável. É outro problema quando, geralmente, a refatoração é a última medida corretiva necessária.
fonte
java.awt.Toolkit
. Meu argumento é o mesmo:Toolbox
soa como uma sacola de pedaços não relacionados, em vez de uma classe coerente com um único objetivo. Não parece um bom design para mim. (Note-se que o artigo que você se refere é a partir de 2001, antes da injeção de dependência e recipientes DI tinha se tornado comum.)Meu principal problema com o padrão de design singleton é que é muito difícil escrever bons testes de unidade para seu aplicativo.
Todo componente que tem uma dependência desse "gerenciador" faz isso consultando sua instância singleton. E se você quiser escrever um teste de unidade para esse componente, precisará injetar dados nessa instância singleton, o que pode não ser fácil.
Se, por outro lado, seu "gerente" for injetado nos componentes dependentes por meio de um parâmetro construtor, e o componente não souber o tipo concreto do gerente, apenas uma interface ou classe base abstrata que o gerente implementa, uma unidade O teste poderia fornecer implementações alternativas do gerente ao testar dependências.
Se você usar contêineres IOC para configurar e instanciar os componentes que compõem seu aplicativo, poderá configurar facilmente seu contêiner IOC para criar apenas uma instância do "gerenciador", permitindo obter o mesmo, apenas uma instância controlando o cache global de aplicativos .
Mas se você não se importa com testes de unidade, um padrão de design único pode ser perfeitamente adequado. (mas eu não faria isso de qualquer maneira)
fonte
Um singleton não é fundamentalmente ruim , no sentido de que qualquer coisa que a computação de design possa ser boa ou ruim. Só pode estar correto (fornece os resultados esperados) ou não. Também pode ser útil ou não, se tornar o código mais claro ou mais eficiente.
Um caso em que singletons são úteis é quando eles representam uma entidade que é realmente única. Na maioria dos ambientes, os bancos de dados são únicos, na verdade existe apenas um banco de dados. A conexão com esse banco de dados pode ser complicada porque requer permissões especiais ou atravessar vários tipos de conexão. Organizar essa conexão em um singleton provavelmente faz muito sentido apenas por esse motivo.
Mas você também precisa ter certeza de que o singleton é realmente um singleton, e não uma variável global. Isso é importante quando o banco de dados único e único é realmente de 4 bancos de dados, um para os equipamentos de produção, preparação, desenvolvimento e teste. Um Singleton do banco de dados descobrirá a quem ele deve se conectar, pegue a instância única desse banco de dados, conecte-o, se necessário, e devolva-o ao chamador.
Quando um singleton não é realmente um singleton (é quando a maioria dos programadores fica chateado), é um global preguiçosamente instanciado, não há oportunidade de injetar uma instância correta.
Outra característica útil de um padrão singleton bem projetado é que ele geralmente não é observável. O chamador pede uma conexão. O serviço que fornece pode retornar um objeto em pool ou, se estiver executando um teste, pode criar um novo para cada chamador ou fornecer um objeto simulado.
fonte
O uso do padrão singleton que representa objetos reais é perfeitamente aceitável. Eu escrevo para o iPhone e há muitos singletons na estrutura do Cocoa Touch. O aplicativo em si é representado por um singleton da classe
UIApplication
. Como você é apenas um aplicativo, é apropriado representá-lo com um singleton.Usar um singleton como uma classe de gerenciador de dados é bom, desde que seja projetado corretamente. Se é um balde de propriedades de dados, isso não é melhor que o escopo global. Se é um conjunto de getters e setters, é melhor, mas ainda não é ótimo. Se é uma classe que realmente gerencia toda a interface de dados, incluindo talvez buscar dados remotos, cache, configuração e desmontagem ... Isso pode ser muito útil.
fonte
Singletons são apenas a projeção de uma arquitetura orientada a serviços em um programa.
Uma API é um exemplo de um singleton no nível do protocolo. Você acessa o Twitter, o Google etc. por meio de essencialmente singletons. Então, por que os singletons se tornam ruins dentro de um programa?
Depende de como você pensa em um programa. Se você pensa em um programa como uma sociedade de serviços, em vez de instâncias em cache vinculadas aleatoriamente, os singletons fazem todo o sentido.
Singletons são um ponto de acesso ao serviço. A interface pública para uma biblioteca de funcionalidades bem restrita que talvez oculte uma arquitetura interna muito sofisticada.
Portanto, não vejo um singleton tão diferente de uma fábrica. O singleton pode ter parâmetros de construtor passados. Ele pode ser criado por algum contexto que sabe como resolver a impressora padrão em relação a todos os mecanismos de seleção possíveis, por exemplo. Para testar, você pode inserir seu próprio mock. Portanto, pode ser bastante flexível.
A chave está internamente em um programa quando executo e preciso de um pouco de funcionalidade. Posso acessar o singleton com total confiança de que o serviço está instalado e pronto para uso. Essa é a chave quando existem diferentes threads iniciando em um processo que devem passar por uma máquina de estado para serem considerados prontos.
Normalmente, eu envolvia uma
XxxService
classe que envolve um singleton na classeXxx
. O singleton não está na classeXxx
, é separado em outra classeXxxService
,. Isso ocorre porqueXxx
podemos ter várias instâncias, embora não seja provável, mas ainda queremos ter umaXxx
instância globalmente acessível em cada sistema.XxxService
fornece uma boa separação de preocupações.Xxx
não precisa impor uma política de singleton, mas podemos usarXxx
como singleton quando necessário.Algo como:
fonte
Primeira pergunta, você encontra muitos erros no aplicativo? talvez esquecendo de atualizar o cache ou um cache ruim ou ache difícil mudar? (Lembro-me de que um aplicativo não mudaria de tamanho, a menos que você também alterasse a cor ... você pode alterar a cor novamente e manter o tamanho).
O que você faria é ter essa classe, mas REMOVER TODOS OS MEMBROS ESTÁTICOS. Ok, isso não é necessário, mas eu recomendo. Realmente, você apenas inicializa a classe como uma classe normal e passa o ponteiro. Não diga ClassIWant.APtr (). LetMeChange.ANYTHINGATALL (). Andhave_no_structure ()
É mais trabalho, mas realmente, é menos confuso. Alguns lugares onde você não deve mudar as coisas que não pode agora, já que não são mais globais. Todas as minhas aulas de gerente são classes regulares, apenas trate-as assim.
fonte
IMO, seu exemplo parece bom. Eu sugeriria fatorar o seguinte: objeto de cache para cada (e atrás de cada) objeto de dados; objetos de cache e objetos de acessador de banco de dados têm a mesma interface. Isso permite trocar caches dentro e fora do código; Além disso, oferece uma rota de expansão fácil.
Gráfico:
O acessador e o cache do banco de dados podem herdar do mesmo objeto ou tipo de pato e parecer com o mesmo objeto, qualquer que seja. Contanto que você possa conectar / compilar / testar e ainda funcione.
Isso desacopla as coisas para que você possa adicionar novos caches sem precisar entrar e modificar algum objeto Uber-Cache. YMMV. IANAL. ETC.
fonte
Um pouco atrasado para a festa, mas de qualquer maneira.
Singleton é uma ferramenta em uma caixa de ferramentas, como qualquer outra coisa. Espero que você tenha mais na sua caixa de ferramentas do que apenas um martelo.
Considere isto:
vs
1º caso leva a acoplamento alto etc; O segundo caminho não tem problemas @Aaronaught está descrevendo, até onde eu sei. É tudo sobre como você o usa.
fonte
Peça a cada tela que apareça no Manager em seu construtor.
Ao iniciar seu aplicativo, você cria uma instância do gerente e a repassa.
Isso se chama Inversão de controle e permite que você troque o controlador quando a configuração for alterada e nos testes. Além disso, você pode executar várias instâncias do aplicativo ou partes dele em paralelo (bom para testar!). Por fim, seu gerente morrerá com seu próprio objeto (a classe de inicialização).
Portanto, estruture seu aplicativo como uma árvore, onde as coisas acima possuem tudo o que é usado abaixo delas. Não implemente um aplicativo como uma malha, onde todos conhecem todos e se encontram através de métodos globais.
fonte