Alternativas a Singletons / Globais

16

Já ouvi inúmeras vezes sobre as armadilhas dos singletons / globais, e entendo por que eles são tão frequentemente desaprovados.

O que não entendo é qual é a alternativa elegante e não bagunçada. Parece que a alternativa ao uso de Singletons / globais sempre envolve a passagem de objetos em um milhão de níveis através dos objetos de seu mecanismo até que eles atinjam os objetos que precisam deles.

Por exemplo, no meu jogo, pré-carrego alguns ativos quando o jogo é iniciado. Esses ativos não são usados ​​até muito mais tarde, quando o jogador navega pelo menu principal e entra no jogo. Devo passar esses dados do meu objeto Game, para o meu objeto ScreenManager (apesar de apenas uma tela realmente se importar com esses dados), para o objeto Screen apropriado e para qualquer outro lugar?

Parece que estou trocando dados de estado global por injeção de dependência desordenada, passando dados para objetos que nem se importam com os dados, exceto com o objetivo de repassá-los a objetos filho.

Este é um caso em que um Singleton seria uma coisa boa ou há alguma solução elegante que me falta?

vargoniano
fonte

Respostas:

16

Não confunda singletons e globais. Embora algum tipo de variável global seja geralmente necessário, o singleton não é apenas um substituto para uma variável global, mas principalmente uma maneira de solucionar problemas de ordem de inicialização estática em C ++ ( e FQA ). (Em outros idiomas, é uma maneira de solucionar diferentes deficiências de idioma, como a falta de variáveis ​​globais e funções simples.)

Se você pode simplesmente usar um ponteiro global em vez de um singleton, e garantir que ele seja inicializado (manualmente) antes que qualquer coisa precise, evite a chamada de função e a sobrecarga de ramificação, a sintaxe coxa para chegar ao objeto e você pode realmente fazer uma segunda instância da classe quando for necessário para testes ou porque seu design foi alterado.

Para as poucas variáveis ​​globais que você deseja (exemplos comuns são saída de áudio, lista de janelas abertas, manipulador de teclado etc.), recomendo o padrão do localizador de serviço . Facilita a substituição de itens por diferentes implementações (por exemplo, dispositivo de áudio real vs. nulo) e coleta todos os seus globais em uma estrutura para evitar poluir o seu espaço para nome.


fonte
+1. Boa resposta e obrigado pelo link do padrão do localizador de serviço. Essa é uma leitura muito interessante.
bummzack
1

Se você não pode / não pode ter uma parte do código que "sabe" magicamente sobre alguns dados, eles precisam ser passados ​​de alguma forma. No entanto, isso não significa que deve necessariamente passar apenas por argumentos.

No seu exemplo de exemplo, você não poderia ter algum tipo de "AssetManager" que carregasse e armazenasse os ativos e, então, o ScreenManager precisaria apenas de uma referência a isso (provavelmente na criação)? Nesse sentido, você está passando as referências aos ativos agrupados em outro objeto e pode passar isso uma vez, na inicialização, em vez de passá-lo para a função folha quando necessário.

Agora, IMHO, que o AssetManager, sendo o tipo de coisa que você só quer, pode muito bem ser um singleton. Desde que você entenda as armadilhas e codifique-as especificamente para evitá-las (suponha que o singleton seja acessado simultaneamente a partir de vários threads e apunhale-se com um garfo sempre que fizer algo que precisa bloquear), e se nocauteie.

JasonD
fonte
1

Eu acho que Jason D está absolutamente certo - é assim que eu lidaria com isso:

O jogo possui uma instância do AssetManager, um objeto do qual você pode obter qualquer ativo pelo nome.

No jogo:

assetManager = new AssetManager();
screenManager = new ScreenManager();
screenManager.assetManager = assetManager;

No ScreenManager:

screen = new Screen();
screen.assetManager = assetManager;

Na tela:

myAsset = assetManager.getBitmp("lava.png");

Agora todas as telas têm acesso a todos os ativos necessários. Isso não é mais complexo ou louco do que usar globals ou singletons, e você tem a opção de ter duas instâncias do jogo em execução no mesmo aplicativo sem conflitos. Certa vez, tive que criar um jogo composto por 8 minijogos, todos compartilhando as mesmas classes / estruturas básicas. Eu tive que refatorar todos os meus globals / singletons para usar esse estilo de referência e nunca olhei para trás. As únicas coisas que devem ser globais são coisas que só podem existir fisicamente uma vez, como áudio, redes, E / S etc.

Iain
fonte
0

Você pode usar o padrão de fábrica para substituir o Singleton . Em seguida, a classe factory tem o controle sobre quantas instâncias você pode criar, as quais você pode alterar facilmente mais tarde quando achar que precisa de mais de uma AssetManager. Conforme declarado neste artigo :

Dá a você toda a flexibilidade do Singleton, com quase nenhum problema.


Outra possibilidade bastante limitada é tornar a classe estática (o que não acho viável para um AssetManager e apenas possível em linguagens que tenham classes estáticas). Mas isso funciona apenas se você não precisar de herança / polimorfismo. É uma solução muito inflexível:

métodos estáticos são tão flexíveis quanto granito. Toda vez que você usa um, está lançando parte do seu programa em concreto. Apenas certifique-se de que você não está com o pé preso lá enquanto o vê endurecer. Algum dia você ficará surpreso ao saber que, realmente, você realmente precisa de outra implementação dessa classe dang PrintSpooler, e ela deveria ter sido uma interface, uma fábrica e um conjunto de classes de implementação. D'oh!

Trata-se de métodos estáticos, mas também pode ser aplicado a classes estáticas.

Michael Klement
fonte
O que você quer dizer com "tornar a classe estática"? C ++ não tem classes estáticas. Você quer dizer apenas ter métodos estáticos? Por que se preocupar em ter uma classe em vez de um namespace?
1
@ Joe: Bem, a questão não está focada em C ++ como eu a entendi. Em C # ou Java, você pode criar classes estáticas e estou me referindo a elas. Além disso, como eu disse, as classes estáticas não são uma solução ideal na maioria das vezes, mas em casos raros, podem funcionar como um global.
Michael Klement