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.
fonte
Respostas:
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).
fonte
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:
É assim, usando o padrão Service Locator:
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.
fonte
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,
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:
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 .
fonte
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.
fonte
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.
fonte
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.
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:
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.
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.
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?
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.
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.
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.
fonte
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 ...
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) ...
E uso ...
De qualquer lugar eu posso fazer algo como
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.
fonte
var tService = Sys.Game.getService<T>(); tService.Something();
vez de pular agetService
parte e acessá-lo diretamente?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
HashMap
com .NETDictionary
. Ambos são bem parecidos, mas têm uma diferença fundamental: o JavaHashMap
é codificadoequals
ehashCode
(efetivamente usa um comparador de singleton), enquanto o .NETDictionary
pode aceitar uma instânciaIEqualityComparer<T>
como parâmetro construtor. O custo em tempo de execução de manter oIEqualityComparer
é mínimo, mas a complexidade que ele introduz não é.Como cada
Dictionary
instância encapsula umIEqualityComparer
, 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ânciasd1
ed2
deDictionary
hold referenciarem a mesmaIEqualityComparer
, será possível produzir uma nova instânciad3
que, para qualquerx
,d3.ContainsKey(x)
seja iguald1.ContainsKey(x) || d2.ContainsKey(x)
. Se, no entanto,d1
ed2
puder 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.
fonte