Existem muitas razões pelas quais os globais são maus no POO.
Se o número ou tamanho dos objetos que precisam ser compartilhados for muito grande para ser transmitido com eficiência nos parâmetros de função, geralmente todos recomendam a Injeção de Dependências, em vez de um objeto global.
No entanto, no caso em que quase todo mundo precisa saber sobre uma determinada estrutura de dados, por que a Injeção de Dependência é melhor do que um objeto global?
Exemplo (simplificado, para mostrar o ponto em geral, sem se aprofundar muito em uma aplicação específica)
Existem vários veículos virtuais que possuem um grande número de propriedades e estados, de tipo, nome, cor, velocidade, posição etc. Vários usuários podem controlá-los remotamente e um grande número de eventos (ambos iniciado e automático) pode alterar muitos de seus estados ou propriedades.
A solução ingênua seria apenas criar um contêiner global deles, como
vector<Vehicle> vehicles;
que pode ser acessado de qualquer lugar.
A solução mais amigável ao OOP seria fazer com que o contêiner fosse membro da classe que lida com o loop do evento principal e instanciado em seu construtor. Toda classe que precisar, e for membro do thread principal, terá acesso ao contêiner por meio de um ponteiro em seu construtor. Por exemplo, se uma mensagem externa chegar por meio de uma conexão de rede, uma classe (uma para cada conexão) que manipula a análise assumirá o controle e o analisador terá acesso ao contêiner por meio de um ponteiro ou referência. Agora, se a mensagem analisada resultar em uma alteração em um elemento do contêiner ou exigir alguns dados dele para executar uma ação, ela poderá ser tratada sem a necessidade de passar milhares de variáveis por sinais e slots (ou pior, armazená-los no analisador para serem recuperados mais tarde por quem chamou o analisador). Obviamente, todas as classes que recebem acesso ao contêiner via injeção de dependência fazem parte do mesmo encadeamento. Threads diferentes não acessam diretamente, mas fazem seu trabalho e enviam sinais para o thread principal, e os slots no thread principal atualizam o contêiner.
No entanto, se a maioria das classes terá acesso ao contêiner, o que o torna realmente diferente de um global? Se tantas classes precisam dos dados no contêiner, a "maneira de injeção de dependência" não é apenas um global disfarçado?
Uma resposta seria a segurança do encadeamento: mesmo que eu tenha cuidado para não abusar do contêiner global, talvez outro desenvolvedor no futuro, sob pressão de um prazo próximo, use o contêiner global em um encadeamento diferente, sem cuidar de tudo os casos de colisão. No entanto, mesmo no caso de injeção de dependência, um poderia apontar para alguém executando em outro thread, levando aos mesmos problemas.
Respostas:
A injeção de dependência é a melhor coisa desde o pão fatiado , enquanto há décadas se sabe que os objetos globais são a fonte de todo o mal ; portanto, essa é uma pergunta bastante interessante.
O ponto de injeção de dependência não é simplesmente garantir que todo ator que precisa de algum recurso possa tê-lo, porque obviamente, se você tornar todos os recursos globais, todo ator terá acesso a todos os recursos, problema resolvido, certo?
O ponto de injeção de dependência é:
O fato de que, em sua configuração específica, todos os atores precisam acessar a mesma instância de recurso é irrelevante. Confie em mim, um dia você precisará reconfigurar as coisas para que os atores tenham acesso a diferentes instâncias do recurso e, em seguida, perceberá que se pintou em um canto. Algumas respostas já apontaram essa configuração: testing .
Outro exemplo: suponha que você divida seu aplicativo em cliente-servidor. Todos os atores no cliente usam o mesmo conjunto de recursos centrais no cliente e todos os atores no servidor usam o mesmo conjunto de recursos centrais no servidor. Agora, suponha que um dia você decida criar uma versão "independente" do seu aplicativo cliente-servidor, em que o cliente e o servidor são empacotados em um único executável e em execução na mesma máquina virtual. (Ou ambiente de tempo de execução, dependendo do idioma de sua escolha.)
Se você usar injeção de dependência, poderá facilmente garantir que todos os atores do cliente recebam as instâncias de recursos do cliente, enquanto todos os atores do servidor recebem as instâncias de recursos do servidor.
Se você não usar a injeção de dependência, estará completamente sem sorte, pois apenas uma instância global de cada recurso pode existir em uma máquina virtual.
Então, você deve considerar: todos os atores realmente precisam de acesso a esse recurso? mesmo?
É possível que você tenha cometido o erro de transformar esse recurso em um objeto divino (então é claro que todos precisam ter acesso a ele) ou talvez você esteja superestimando o número de atores em seu projeto que realmente precisam de acesso a esse recurso .
Com os globais, todas as linhas de código-fonte em todo o aplicativo têm acesso a todos os recursos globais. Com a injeção de dependência, cada instância de recurso é visível apenas para os atores que realmente precisam. Se os dois forem iguais (os atores que precisam de um recurso específico compreendem 100% das linhas de código-fonte do seu projeto), você deve ter cometido um erro no seu design. Então, também
Refatorar esse grande e enorme recurso divino em sub-recursos menores, para que diferentes atores precisem acessar partes diferentes, mas raramente um ator precisa de todas as suas peças, ou
Refatorar seus atores para, por sua vez, aceitar como parâmetros apenas os subconjuntos do problema em que eles precisam trabalhar, para que eles não precisem consultar um grande recurso central imenso o tempo todo.
fonte
Essa é uma afirmação duvidosa. O link que você usar como evidência refere-se ao estado - mutável globals. Eles são decididamente maus. Somente leitura globais são apenas constantes. As constantes são relativamente sãs. Quero dizer, você não vai injetar o valor de pi em todas as suas classes, certo?
Não, eles não.
Se suas funções / classes têm muitas dependências, as pessoas recomendam parar e dar uma olhada no motivo pelo qual seu design é tão ruim. Ter um monte de dependências é um sinal de que sua classe / função provavelmente está fazendo muitas coisas e / ou você não possui abstração suficiente.
Este é um pesadelo de design. Você tem um monte de coisas que podem ser usadas / abusadas sem nenhuma forma de validação, sem limitações de simultaneidade - é apenas um acoplamento.
Sim, o acoplamento a dados comuns em todos os lugares não é muito diferente de um global e, como tal, ainda é ruim.
A vantagem é que você está dissociando os consumidores da própria variável global. Isso fornece uma certa flexibilidade na forma como a instância é criada. Também não força você a ter apenas uma instância. Você pode fazer com que diferentes sejam repassados a seus diferentes consumidores. Você também pode criar implementações diferentes da sua interface por consumidor, se necessário.
E devido às mudanças que inevitavelmente acompanham os requisitos de mudança do software, um nível básico de flexibilidade é vital .
fonte
foo
s"?Há três razões principais que você deve considerar.
Então, resumindo: o DI não é uma meta, é apenas uma ferramenta que possibilita alcançar uma meta. Se você usá-lo mal (como parece fazer no seu exemplo), não se aproximará do objetivo, não há nenhuma propriedade mágica do DI que tornará um bom design ruim.
fonte
É realmente sobre testabilidade - normalmente um objeto global é muito sustentável e fácil de ler / entender (quando você o conhece, é claro), mas é um elemento permanente e, quando você divide suas aulas em testes isolados, descobre que seu o objeto global está parado dizendo "e quanto a mim" e, portanto, seus testes isolados exigem que o objeto global seja incluído neles. Não ajuda que a maioria das estruturas de teste não suporte facilmente esse cenário.
Então, sim, ainda é global. O motivo fundamental para tê-lo não mudou, apenas a maneira como é acessado.
Quanto à inicialização, isso ainda é um problema - você ainda precisa definir a configuração para que seus testes possam ser executados, para que você não esteja ganhando nada lá. Suponho que você possa dividir um objeto dependente grande em muitos menores e passar o apropriado para as classes que precisam deles, mas você provavelmente está obtendo ainda mais complexidade para compensar quaisquer benefícios.
Independentemente de tudo isso, é um exemplo da 'cauda abanando o cachorro', a necessidade de testar está determinando como a base de código é arquitetada (problemas semelhantes surgem com itens como classes estáticas, por exemplo, data e hora atuais).
Pessoalmente, prefiro colar todos os meus dados globais em um objeto global (ou vários), mas forneço acessadores para obter os bits que minhas classes precisam. Então eu posso zombar deles para retornar os dados que eu quero quando se trata de teste sem a complexidade adicional do DI.
fonte
Além das respostas que você já tem,
Uma das características da programação OOP é manter apenas as propriedades necessárias para um objeto específico,
AGORA SE o seu objeto tem muitas propriedades - você deve dividi-lo em subobjetos.
Editar
Já mencionado por que os objetos globais são ruins, eu adicionaria um exemplo a ele.
Você precisa fazer testes de unidade no código da GUI de um formulário do Windows; se você usar global, precisará criar todos os botões e seus eventos, por exemplo, para criar um caso de teste adequado ...
fonte