Como posso evitar muitos singletons na minha arquitetura de jogos?

55

Eu uso o mecanismo de jogo cocos2d-x para criar jogos. O mecanismo já usa muitos singletons. Se alguém o usou, deve estar familiarizado com alguns deles:

Director
SimpleAudioEngine
SpriteFrameCache
TextureCache
EventDispatcher (was)
ArmatureDataManager
FileUtils
UserDefault

e muito mais com cerca de 16 classes. Você pode encontrar uma lista semelhante nesta página: Objetos Singleton no Cocos2d-html5 v3.0 Mas quando eu quero escrever um jogo, preciso de muito mais singletons:

PlayerData (score, lives, ...)
PlayerProgress (passed levels, stars)
LevelData (parameters per levels and level packs)
SocialConnection (Facebook and Twitter login, share, friend list, ...)
GameData (you may obtain some data from server to configure the game)
IAP (for in purchases)
Ads (for showing ads)
Analytics (for collecting some analytics)
EntityComponentSystemManager (mananges entity creation and manipulation)
Box2dManager (manages the physics world)
.....

Por que eu acho que eles deveriam ser singletons? Porque precisarei deles em lugares muito diferentes no meu jogo, e o acesso compartilhado seria muito útil. Em outras palavras, não sei o que criar em algum lugar e passo os ponteiros para toda a minha arquitetura, pois será muito difícil. Além disso, essas são coisas que eu preciso de apenas uma. De qualquer forma, precisarei de vários, também posso usar um padrão Multiton. Mas o pior é que Singleton é o padrão mais criticado por causa de:

 - bad testability
 - no inheritance available
 - no lifetime control
 - no explicit dependency chain
 - global access (the same as global variables, actually)
 - ....

Você pode encontrar algumas idéias aqui: https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons e https://stackoverflow.com/questions/4074154/when-should-the-singleton -padrão não deve ser usado além do óbvio

Então, eu acho, estou fazendo algo errado. Eu acho que meu código cheira . :) Eu acho que os desenvolvedores de jogos mais experientes resolvem esse problema de arquitetura? Quero verificar, talvez ainda seja normal no desenvolvimento de jogos ter mais de 30 singletons , considerados os que já estão no mecanismo de jogo.

Eu pensei em usar uma Fachada Singleton, que terá instâncias de todas essas classes de que preciso, mas cada uma delas já não seria singleton. Isso eliminará muitos problemas, e eu teria apenas um singleton que seria a própria fachada. Mas, neste caso, terei outro problema de design. A fachada se tornará um objeto de Deus. Eu acho que isso cheira também . Portanto, não consigo encontrar uma boa solução de design para essa situação. Conselho por favor.

Narek
fonte
26
A maioria das pessoas que argumentam contra o Singleton parece não perceber que o trabalho necessário para propagar alguns dados / funcionalidades por todo o código pode ser mais uma fonte de bug do que usar um Singleton. Então, basicamente, eles não estão comparando, apenas rejeitando ==> suspeito que o anti-Singleton seja uma Postura :-) Então, Singleton tem falhas, por isso, se você pode evitá-lo, evite os problemas que o acompanham , agora, se não puder, faça isso sem olhar para trás. Afinal, Cocos2d parece funcionar muito bem com seus 16 Singletons ...
GameAlchemist
6
Como lembrete, esta é uma pergunta sobre como executar uma tarefa específica. Esta não é uma pergunta que solicite uma discussão pró / contrária de singletons (que seria fora de tópico aqui de qualquer maneira). Além disso, lembre-se de que os comentários devem ser usados ​​para solicitar esclarecimentos ao solicitante, não como uma plataforma para uma discussão tangencial sobre os prós e os contras dos singletons. Se você deseja fornecer sua opinião, a maneira correta de fazer isso é fornecer uma resposta legítima à pergunta , na qual você pode moderar sua solução com conselhos a favor ou contra o padrão singleton.
Josh
Suponho que esses singletons sejam acessíveis globalmente? Qual seria um dos motivos para evitar esse padrão.
Peter - Restabelece Monica

Respostas:

41

Inicializo meus serviços na minha classe de aplicativo principal e os passo como ponteiros para o que for necessário para usá-los por meio de construtores ou funções. Isso é útil por dois motivos.

Primeiro, a ordem de inicialização e limpeza é simples e clara. Não há como inicializar acidentalmente um serviço em outro lugar como você pode com um singleton.

Segundo, é claro quem depende de quê. Eu posso ver facilmente quais classes precisam de quais serviços apenas na declaração de classe.

Você pode achar irritante passar todos esses objetos, mas se o sistema for bem projetado, não será realmente ruim e tornará as coisas muito mais claras (para mim, de qualquer maneira).

megadan
fonte
34
+1. Além disso, o "aborrecimento" de ter que passar referências a sistemas ao redor ajuda a combater o aumento da dependência; se você tiver que passar algo para " tudo " , isso é um bom sinal de que você tem um problema de acoplamento ruim no seu design e é muito mais explícito dessa maneira do que com acesso global promíscuo a tudo, de qualquer lugar.
Josh
2
Na verdade, isso não fornece uma solução ... então você tem sua classe de programa com um monte de objetos singleton e agora com 10 níveis de código em algo na cena precisa de um desses singletons ... como é passado?
War
Wardy: Por que eles precisariam ser singletons? Claro, principalmente você pode estar passando por uma instância específica, mas o que torna um singleton um singleton é que você NÃO PODE usar uma instância diferente. Com a injeção, você pode passar uma instância para um objeto e outra para o próximo. Só porque você não acaba fazendo isso por qualquer motivo, não significa que não poderia.
1/14
3
Eles não são singletons. Eles são objetos regulares, como qualquer outra coisa, com um proprietário bem definido que controla o init / cleanup. Se você tem 10 níveis de profundidade sem acesso, talvez tenha um problema de design. Nem tudo precisa ou deve ter acesso a renderizadores, áudio e outros. Dessa forma, você pensa em seu design. Como um bônus adicional para aqueles que testam seu código (o quê?), O teste de unidade também se torna muito mais fácil.
megadan
2
Sim, acho que a injeção de dependência (com ou sem um framework como o Spring) é uma solução perfeita para lidar com isso. Eu encontrei este para ser um grande artigo para aqueles que querem saber como um código melhor estrutura para evitar passar as coisas em todos os lugares: misko.hevery.com/2008/10/21/...
megadan
17

Não vou discutir sobre a maldade por trás dos singletons, porque a Internet pode fazer isso melhor do que eu.

Nos meus jogos, uso o padrão Service Locator para evitar toneladas de Singletons / Gerentes.

O conceito é muito simples. Você só tem um Singleton que age como a única interface para alcançar o que costumava usar como Singleton. Em vez de ter vários Singleton, agora você tem apenas um.

Chamadas como:

FlyingToasterManager.GetInstance().Toast();
SmokeAlarmManager.GetInstance().Start();

É assim, usando o padrão Service Locator:

SvcLocator.FlyingToaster.Toast();
SvcLocator.SmokeAlarm.Start();

Como você pode ver, todos os Singleton estão contidos no Service Locator.

Para ter uma explicação mais detalhada, sugiro que você leia esta página sobre como usar o Padrão do Localizador .

[ EDIT ]: como apontado nos comentários, o site gameprogrammingpatterns.com também contém uma seção muito interessante sobre Singleton .

Espero que ajude.

lvictorino
fonte
4
O site vinculado aqui, gameprogrammingpatterns.com, também possui um capítulo sobre Singletons que parece relevante.
precisa saber é o seguinte
28
Os localizadores de serviço são apenas singletons que movem todos os problemas normais para o tempo de execução, em vez de compilar. No entanto, o que você está sugerindo é apenas um registro estático gigante, que é quase melhor, mas ainda essencialmente uma massa de variáveis ​​globais. Isso é simplesmente uma questão de arquitetura ruim, se muitos níveis precisarem acessar os mesmos objetos. Injeção de dependência adequada é o que é necessário aqui, não diferentemente nomeado global do que o atual global.
Magus
2
Você pode elaborar sobre "injeção de dependência adequada"?
Blue Wizard
17
Isso realmente não ajuda na questão. São essencialmente muitos singletons fundidos em um singleton gigante. Nenhum dos problemas estruturais subjacentes foi corrigido ou mesmo resolvido; o código ainda fede sua aparência agora mais bonita.
ClassicThunder
4
@classicthunder Embora isso não resolva todos os problemas, essa é uma grande melhoria em relação aos Singletons, pois aborda vários. Em particular, o código que você escreve não é mais acoplado a uma única classe, mas a uma interface / categoria de classes. Agora você pode facilmente trocar diferentes implementações dos vários serviços.
Jhocking
12

Lendo todas essas respostas, comentários e artigos apontados, especialmente esses dois artigos brilhantes,

finalmente, cheguei à seguinte conclusão, que é uma espécie de resposta para minha própria pergunta. A melhor abordagem é não ser preguiçoso e passar dependências diretamente. Isso é explícito, testável, não há acesso global, pode indicar um design incorreto se você precisar passar por delegados muito profundos e em muitos lugares e assim por diante. Mas na programação, um tamanho não serve para todos. Assim, ainda existem casos em que não é útil passá-los diretamente. Por exemplo, no caso de criarmos uma estrutura de jogo (um modelo de código que será reutilizado como código base para desenvolver diferentes tipos de jogos), e incluiremos muitos serviços. Aqui, não sabemos até que ponto cada serviço pode ser passado e quantos lugares serão necessários. Depende de um jogo em particular. Então, o que fazemos neste tipo de casos? Usamos o padrão de design do Localizador de Serviço em vez de Singleton,

  1. Você não programa para as implementações, mas para as interfaces. Isso é muito essencial em termos de princípios de OOP. No tempo de execução, você pode alterar ou até desativar o serviço usando o padrão Objeto Nulo. Isso não é apenas importante em termos de flexibilidade, mas ajuda diretamente nos testes. Você pode desativar, simular, também pode usar o padrão Decorator, para adicionar alguns logs e outras funcionalidades também. Esta é uma propriedade muito importante, que Singleton não possui.
  2. Outra grande vantagem é que você pode controlar a vida útil do objeto. Em Singleton, você controla apenas o tempo em que o objeto será gerado pelo padrão Lazy Initialization. Porém, uma vez que o objeto seja gerado, ele permanecerá até o final do programa. Mas, em alguns casos, você gostaria de alterar o serviço. Ou até desligá-lo. Especialmente nos jogos, essa flexibilidade é muito importante.
  3. Combina / envolve vários Singletons em uma API compacta. Eu acho que você também pode usar algumas Fachadas como Localizadores de Serviço, que para uma única operação podem usar vários serviços ao mesmo tempo.
  4. Você pode argumentar que ainda temos o acesso global, que é o principal problema de design do Singleton. Concordo, mas precisaremos apenas desse padrão e somente se quisermos algum acesso global. Não devemos reclamar do acesso global, se acharmos que a injeção direta de dependência não é útil para a nossa situação e precisamos de um acesso global. Mas mesmo para esse problema, uma melhoria está disponível (consulte o segundo artigo acima). Você pode tornar todos os serviços privados e herdar da classe Service Locator todas as classes que acha que eles devem usar os serviços. Isso pode restringir significativamente o acesso. E considere que, no caso de Singleton, você não pode usar herança por causa do construtor privado.

Também devemos considerar que esse padrão foi usado no Unity, LibGDX, XNA. Isso não é uma vantagem, mas é uma espécie de prova da usabilidade do padrão. Considere que esses mecanismos foram desenvolvidos por muitos desenvolvedores inteligentes por um longo tempo e, durante as fases de evolução do mecanismo, eles ainda não encontraram uma solução melhor.

Concluindo, acho que o Service Locator pode ser muito útil para os cenários descritos na minha pergunta, mas ainda deve ser evitado se for possível. Como regra geral - use-a se for necessário.

EDIT: Após um ano de trabalho e usando DI direta e tendo problemas com grandes construtores e muitas delegações, fiz mais pesquisas e encontrei um bom artigo que fala sobre prós e contras sobre os métodos de DI e Service Locator. Citando este artigo ( http://www.martinfowler.com/articles/injection.html ) de Martin Fowler que explica quando realmente os localizadores de serviço são ruins:

Portanto, o principal problema é para as pessoas que estão escrevendo código que espera ser usado em aplicativos fora do controle do gravador. Nesses casos, mesmo uma suposição mínima sobre um localizador de serviço é um problema.

Mas, de qualquer maneira, se você quiser que façamos DI pela primeira vez, leia este artigo http://misko.hevery.com/2008/10/21/dependency-injection-myth-reference-passing/ (apontado por @megadan) , prestando muita atenção à Lei de Demeter (LoD) ou princípio de menor conhecimento .

Narek
fonte
Minha intolerância pessoal em relação aos localizadores de serviço surge principalmente das minhas tentativas de testar o código que os utiliza. No interesse do teste de caixa preta, você chama o construtor para criar uma instância para testar. Uma exceção pode ser lançada imediatamente ou em qualquer chamada de método e pode não haver propriedades públicas para levá-lo às dependências ausentes. Isso pode ser pequeno se você tiver acesso à fonte, mas ainda assim viola o Princípio da Menor Surpresa (um problema frequente com Singletons). Você sugeriu a passagem no localizador de serviço, o que alivia parte disso e deve ser elogiado por isso.
Magus
3

Não é incomum que partes de uma base de código sejam consideradas objetos de pedra angular ou uma classe de base, mas isso não justifica que seu ciclo de vida seja ditado como um Singleton.

Os programadores costumam confiar no padrão Singleton como um meio de conveniência e pura preguiça, em vez de adotar a abordagem alternativa e serem um pouco mais detalhados e impor relações objetais entre si de uma maneira explícita.

Então pergunte a si mesmo qual é mais fácil de manter.

Se você é como eu, prefiro poder abrir um arquivo de cabeçalho e percorrer brevemente os argumentos do construtor e os métodos públicos para ver quais dependências um objeto requer, em vez de ter que inspecionar centenas de linhas de código em um arquivo de implementação.

Se você transformou um objeto em um Singleton e depois percebeu que precisa suportar várias instâncias do objeto, imagine as mudanças necessárias necessárias se essa classe fosse algo que você usou muito em toda a sua base de código. A melhor alternativa é tornar as dependências explícitas, mesmo se o objeto for alocado apenas uma vez e se surgir a necessidade de oferecer suporte a várias instâncias, você poderá fazê-lo com segurança sem essas preocupações.

Como outros já apontaram, o uso do padrão Singleton também ofusca o acoplamento, que geralmente significa um design ruim e alta coesão entre os módulos. Essa coesão é geralmente indesejável e leva a códigos quebradiços que podem ser difíceis de manter.

Naros
fonte
-1: Esta resposta descreve erroneamente o padrão singleton e todos os seus argumentos descrevem os problemas com má implementação do padrão. O singleton adequado é uma interface e evita todos os problemas que você descreveu (incluindo a mudança para a não-singeleza, que é um cenário que o GoF aborda explicitamente). Se for difícil refatorar o uso de um singleton, refatorar o uso de uma referência explícita a objetos pode ser apenas mais difícil, e não menos. Se o singleton de alguma forma causa MAIS acoplamento de código, isso simplesmente foi feito errado.
Seth Battin
1

Singleton é um padrão famoso, mas é bom conhecer os propósitos a que serve e seus prós e contras.

Realmente faz sentido se não há nenhum relacionamento .
Se você pode manipular seu componente com um objeto totalmente diferente (sem dependência forte) e esperar ter o mesmo comportamento, o singleton pode ser uma boa opção.

Por outro lado, se você precisar de uma pequena funcionalidade , usada apenas em determinados momentos, deve preferir passar referências .

Como você mencionou, os singletons não têm controle vitalício. Mas a vantagem é que eles têm uma inicialização lenta.
Se você não chamar esse singleton durante a vida útil do aplicativo, ele não será instanciado. Mas duvido que você crie singletons e não os use.

Você deve ver um singleton como um serviço em vez de um componente .

Singletons funciona, eles nunca quebraram nenhuma aplicação. Mas suas carências (as quatro que você deu) são razões pelas quais você não fará uma.

Crazyrems
fonte
1

O mecanismo já usa muitos singletons. Se alguém o usou, deve estar familiarizado com alguns deles:

O desconhecimento não é a razão pela qual os singletons precisam ser evitados.

Há muitas boas razões pelas quais Singleton é necessário ou inevitável. As estruturas de jogos geralmente usam singletons porque é uma consequência inevitável de ter apenas um único hardware com estado. Não faz sentido querer controlar esses hardwares com várias instâncias de seus respectivos manipuladores. A superfície gráfica é um hardware com estado externo e inicializar acidentalmente uma segunda cópia do subsistema gráfico é nada menos que desastroso, já que agora os dois subsistemas gráficos estarão brigando entre si sobre quem deve desenhar e quando, substituindo-se incontrolavelmente. Da mesma forma com o sistema de fila de eventos, eles estarão brigando por quem recebe os eventos do mouse de maneira não-determinística. Ao lidar com um hardware externo com estado no qual existe apenas um deles, o singleton é inevitável para evitar conflitos.

Outro lugar em que Singleton é sensato é com os gerenciadores de cache. Os caches são um caso especial. Eles realmente não devem ser considerados Singleton, mesmo quando usam as mesmas técnicas que os Singletons para permanecer vivo e viver para sempre. Os serviços de armazenamento em cache são transparentes, não devem alterar o comportamento do programa; portanto, se você substituir o serviço de cache por um cache nulo, o programa ainda funcionará, exceto pelo fato de ser executado mais lentamente. A principal razão pela qual os gerenciadores de cache são exceção ao singleton é porque não faz sentido desligar um serviço de cache antes de desligar o próprio aplicativo, porque isso também descartará os objetos em cache, o que impede o ponto de tê-lo como um Singleton.

Essas são boas razões para esses serviços serem singletons. No entanto, nenhum dos bons motivos para ter singletons se aplica às classes que você listou.

Porque precisarei deles em lugares muito diferentes no meu jogo, e o acesso compartilhado seria muito útil.

Essas não são razões para singletons. Essas são razões para os globais. Também é um sinal de mau design se você precisar passar muitas coisas pelos vários sistemas. A necessidade de repassar as coisas indica um alto acoplamento, o que pode ser evitado por um bom design de OO.

Apenas olhando a lista de aulas em seu post que você acha que precisam ser Singletons, posso dizer que metade delas realmente não deve ser singletons e a outra metade parece que elas nem deveriam estar lá em primeiro lugar. O fato de você precisar distribuir objetos parece ser devido à falta de encapsulamento adequado, em vez de bons casos de uso reais para singletons.

Vejamos suas aulas pouco a pouco:


PlayerData (score, lives, ...)
LevelData (parameters per levels and level packs)
GameData (you may obtain some data from server to configure the game)

Apenas pelos nomes das classes, eu diria que essas classes têm cheiro de antipadrão do Modelo de Domínio Anêmico. Objetos anêmicos geralmente resultam em muitos dados que precisam ser passados, o que aumenta o acoplamento e torna o restante do código complicado. Além disso, a classe anêmica oculta o fato de que você provavelmente ainda está pensando procedimentalmente, em vez de usar a orientação a objetos para encapsular detalhes.


IAP (for in purchases)
Ads (for showing ads)

Por que essas classes precisam ser singletons? Parece-me que essas classes devem ter vida curta, que devem ser criadas quando necessárias e desconstruídas quando o usuário concluir a compra ou quando os anúncios não precisarem mais ser exibidos.


EntityComponentSystemManager (mananges entity creation and manipulation)

Em outras palavras, um construtor e uma camada de serviço? Por que essa classe sem limites claros nem propósito existe mesmo em primeiro lugar?


PlayerProgress (passed levels, stars)

Por que o progresso do jogador é separado da classe Player? A classe Player deve saber como acompanhar seu próprio progresso. Se você deseja implementar o rastreamento de progresso em uma classe diferente da classe Player para separar responsabilidades, PlayerProgress deve ficar atrás do Player.


Box2dManager (manages the physics world)

Não posso comentar mais sobre esse assunto sem saber o que essa classe realmente faz, mas uma coisa é clara é que essa classe tem um nome ruim.


Analytics (for collecting some analytics)
SocialConnection (Facebook and Twitter login, share, friend list, ...)

Esta parece ser a única classe em que Singleton pode ser razoável, porque os objetos de conexão social não são realmente seus objetos. As conexões sociais são apenas uma sombra de objetos externos que vivem em um serviço remoto. Em outras palavras, esse é um tipo de cache. Apenas certifique-se de separar claramente a parte de armazenamento em cache com a parte de serviço do serviço externo, porque a última parte não precisa ser um singleton.

Uma maneira de evitar passar instâncias dessas classes é usando a passagem de mensagens. Em vez de fazer uma chamada direta para instâncias dessas classes, você envia uma mensagem endereçada ao serviço Analytic e SocialConnection de forma assíncrona, e esses serviços são assinados para receber essas mensagens e agir sobre a mensagem. Como a fila de eventos já é um singleton, trampolim a chamada, você evita a necessidade de passar uma instância real ao se comunicar com um singleton.

Lie Ryan
fonte
-1

Singletons para mim são sempre ruins.

Na minha arquitetura, criei uma coleção GameServices que a classe do jogo gerencia. Posso pesquisar adicionar e remover desta coleção, conforme necessário.

Ao dizer que algo está no escopo global, você está basicamente dizendo "esse ódio é deus e não tem mestre" nos exemplos que você fornece acima, eu diria que a maioria deles é de classes auxiliares ou de GameServices, caso em que o jogo seria responsável por eles.

Mas esse é apenas o meu design no meu mecanismo, sua situação pode ser diferente.

Colocando mais diretamente ... O único singleton real é o jogo, pois possui todas as partes do código.

Esta resposta é baseada na natureza subjetiva da pergunta e é puramente baseada na minha opinião, pode não estar correta para todos os desenvolvedores por aí.

Edit: Conforme solicitado ...

Ok, isso é da minha cabeça, pois eu não tenho meu código na minha frente no momento, mas essencialmente a raiz de qualquer jogo é a classe Game, que é verdadeiramente um singleton ... deve ser!

Então eu adicionei o seguinte ...

class Game
{
   IList<GameService> Services;

   public T GetService<T>() where T : GameService
   {
       return Services.FirstOrDefatult(s => s is T);
   }
}

Agora também tenho uma classe de sistema (estática) que contém todos os meus singletons de todo o programa (contém muito poucas opções de jogos e configurações e é isso) ...

public static class Sys
{
    // only the main game assembly can set this (done by the game ctor)
    // anything can get
    public static Game Game { get; internal set; }
}

E uso ...

De qualquer lugar eu posso fazer algo como

var tService = Sys.Game.getService<T>();
tService.Something();

Essa abordagem significa que meu jogo "possui" coisas que normalmente seriam consideradas um "singleton" e eu posso estender a classe GameService básica da maneira que desejar. Eu tenho interfaces como IUpdateable que meu jogo verifica e chama para qualquer serviço que o implemente conforme necessário para situações específicas.

Também tenho serviços que herdam / estendem outros serviços para cenários de cena mais específicos.

Por exemplo ...

Eu tenho um serviço de Física que lida com minhas simulações de física, mas dependendo da cena, a simulação de física pode precisar de um comportamento um pouco diferente.

Guerra
fonte
11
Os serviços de jogos não deveriam ser instanciados apenas uma vez? Nesse caso, é apenas um padrão de singleton não imposto no nível da classe, o que é pior do que um singleton implementado corretamente.
GameAlchemist
2
@ Wardy Eu não entendo claramente o que você fez, mas soa como a Fachada Singleton que descrevi e deve se tornar um objeto DEUS, certo?
Narek
10
É apenas um padrão Singleton implementado no nível do jogo. Então, basicamente, o aspecto mais interessante é permitir que você finja que não está usando o Singleton. Útil se seu chefe é da igreja antipadrão.
GameAlchemist
2
Não tenho certeza de como isso é efetivamente diferente de usar Singletons. Não é a única diferença em como você acessa especificamente esse único serviço existente desse tipo? Ou seja, em var tService = Sys.Game.getService<T>(); tService.Something();vez de pular a getServiceparte e acessá-lo diretamente?
Christian
11
@ Christian: Na verdade, é exatamente isso que é o antipadrão do Localizador de Serviço. Aparentemente, essa comunidade ainda acha que é um padrão de design recomendado.
Magus
-1

Um ingrediente essencial da eliminação de singleton, que os oponentes do singleton costumam deixar de considerar é a adição de lógica extra para lidar com o estado muito mais complicado que os objetos encapsulam quando os singletons dão lugar a valores de instância. De fato, mesmo definir o estado de um objeto após substituir um singleton por uma variável de instância pode ser difícil, mas os programadores que substituem singletons por variáveis ​​de instância devem estar preparados para lidar com isso.

Compare, por exemplo, Java HashMapcom .NET Dictionary. Ambos são bem parecidos, mas têm uma diferença fundamental: o Java HashMapé codificado equalse hashCode(efetivamente usa um comparador de singleton), enquanto o .NET Dictionarypode aceitar uma instância IEqualityComparer<T>como parâmetro construtor. O custo em tempo de execução de manter o IEqualityCompareré mínimo, mas a complexidade que ele introduz não é.

Como cada Dictionaryinstância encapsula um IEqualityComparer, seu estado não é apenas um mapeamento entre as chaves que realmente contém e seus valores associados, mas um mapeamento entre os conjuntos de equivalências definidos pelas chaves e o comparador e seus valores associados. Se duas instâncias d1e d2deDictionary hold referenciarem a mesma IEqualityComparer, será possível produzir uma nova instância d3que, para qualquer x, d3.ContainsKey(x)seja igual d1.ContainsKey(x) || d2.ContainsKey(x). Se, no entanto, d1e d2puder conter referências a diferentes comparadores arbitrários de igualdade, em geral não haverá como mesclá-los para garantir que essa condição seja mantida.

Adicionar variáveis ​​de instância em um esforço para eliminar singletons, mas não levar em conta adequadamente os possíveis novos estados que poderiam ser implícitos por essas variáveis ​​de instância pode criar código que acaba sendo mais frágil do que seria com um singleton. Se todas as instâncias de objetos tiverem um campo separado, mas as coisas funcionarem mal, a menos que todas identifiquem a mesma instância, todos os novos campos comprados serão um número crescente de maneiras pelas quais as coisas podem dar errado.

supercat
fonte
11
Embora esse seja definitivamente um aspecto interessante da discussão, não acho que ele realmente atenda à pergunta realmente feita pelo OP.
Josh
11
@ JoshPetrie: Os comentários no post original (por exemplo, por Magus) sugeriram que o custo de execução de referências de referência para evitar o uso de singletons não era grande. Como tal, consideraria relevante o custos semânticos de cada objeto encapsular referências com o objetivo de evitar singletons. Deve-se evitar singletons nos casos em que eles podem ser evitados de maneira barata e fácil, mas o código que não exige abertamente um singleton, mas pode quebrar assim que várias instâncias de algo existe é pior do que o código que usa um singleton.
Supercat 30/09
2
As respostas não são para abordar os comentários, mas para responder diretamente à pergunta.
ClassicThunder
@ClassicThunder: Você gosta mais da edição?
Supercat #