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?

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.


+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.


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.


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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.