Quais são os benefícios da injeção de dependência nos casos em que quase todos precisam acessar uma estrutura de dados comum?

20

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.

vsz
fonte
6
Espere, você fala sobre globais não mutáveis ​​e usa um link para "por que o estado global é ruim" para justificá-lo? WTF?
Telastyn
1
Não estou justificando os globais. Acho claro que concordo com o fato de que os globais não são a boa solução. Estou apenas falando de uma situação em que mesmo o uso de injeção de dependência não pode ser muito melhor do que (ou talvez quase indistinguível de) globais. Assim, uma resposta pode apontar outros benefícios escondidos de ainda usando a injeção de dependência nesta situação, ou que outras abordagens seria melhor do que di
vsz
9
Mas há uma diferença decidida entre globais somente leitura e estado global.
Telastyn 22/09/2015
1
@vsz não é verdade - a questão é sobre as fábricas localizadoras de serviços como uma maneira de contornar o problema de muitos objetos distintos; mas suas respostas também respondem à sua pergunta de permitir que as classes acessem os dados globais em vez dos dados globais que estão sendo passados ​​para as classes.
Gbjbaanb #

Respostas:

37

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?

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 é:

  1. Permitir que os atores acessem recursos de acordo com a necessidade , e
  2. Ter controle sobre qual instância de um recurso é acessada por qualquer ator.

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.

Mike Nakis
fonte
Não sei se entendi isso - por um lado, você diz que o DI permite que você use várias instâncias de um recurso e os globais não, o que é claramente um absurdo, a menos que você esteja confundindo uma única variável estática com objetos instanciados multiplicados - não há razão que "o global" deve ser uma única instância ou uma única classe.
gbjbaanb
3
@gbjbaanb Suponha que o recurso seja um Zork. O OP está dizendo que nem todos os objetos em seu sistema precisam de um Zork para trabalhar, mas também que apenas um Zork existirá e que todos os seus objetos precisarão acessar apenas essa instância do Zork. Estou dizendo que nenhuma dessas duas suposições é razoável: provavelmente não é verdade que todos os seus objetos precisem de um Zork, e haverá momentos em que alguns deles precisarão de uma certa instância de Zork, enquanto outros precisarão de um Zork. instância diferente do Zork.
Mike Nakis
2
@gbjbaanb Não acho que você esteja me pedindo para explicar por que seria estúpido ter duas instâncias globais do Zork, nomeie-as de ZorkA e ZorkB e codifique os objetos em que um deles usará o ZorkA e qual usará o ZorkB, direita?
23615 Mike Nakis
1
Eu não disse todo mundo, eu disse quase todo mundo. O objetivo da pergunta era se o DI tem outros benefícios além de negar o acesso daqueles poucos atores que não precisam dele. Eu sei que "objetos divinos" são um antipadrão, mas seguir as melhores práticas também pode ser uma. Pode acontecer que todo o objetivo de um programa seja fazer várias coisas com um determinado recurso; nesse caso, quase todos precisam acessar esse recurso. Um bom exemplo seria um programa que funcione em uma imagem: quase tudo nele tem algo a ver com os dados da imagem.
vsz 23/09/15
1
@gbjbaanb Acredito que a idéia é ter uma classe de cliente capaz de trabalhar exclusivamente no ZorkA ou no ZorkB, como indicado, para não ter a classe de cliente decidir qual delas pegar.
Eugene Ryabtsev 23/09
39

Existem muitas razões pelas quais globals não mutáveis ​​são maus no POO.

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?

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.

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.

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.

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.

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?

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 .

Telastyn
fonte
desculpe pela confusão com "não mutável", eu queria dizer mutável, e de alguma forma não reconheci meu erro.
vsz 22/09/15
"Este é um pesadelo de design" - eu sei que é. Mas se os requisitos são de que todos esses eventos devem poder executar as alterações nos dados, os eventos serão acoplados aos dados, mesmo que eu os divida e oculte sob uma camada de abstrações. Todo o programa é sobre esses dados. Por exemplo, se um programa precisar executar muitas operações diferentes em uma imagem, todas ou quase todas as suas classes serão de alguma forma acopladas aos dados da imagem. Apenas dizer "vamos escrever um programa que faça algo diferente" não é aceitável.
vsz 22/09/15
2
Os aplicativos que possuem muitos objetos que acessam rotineiramente algum banco de dados não são exatamente inéditos.
Robert Harvey
@RobertHarvey - claro, mas a interface da qual eles dependem "pode ​​acessar um banco de dados" ou "algum armazenamento de dados persistente para foos"?
Telastyn 22/09/2015
1
Me lembra um Dilbert onde o PHB pede 500 recursos e Dilbert diz que não. Então, o PHB pede um recurso e Dilbert diz que sim. No dia seguinte, Dilbert recebe 500 solicitações cada para 1 recurso. Se algo não escala, simplesmente não escala, não importa como você a vista.
precisa saber é o seguinte
16

Há três razões principais que você deve considerar.

  1. Legibilidade. Se cada unidade de código tem tudo o que precisa para trabalhar, injetado ou passado como parâmetro, é fácil olhar para o código e ver instantaneamente o que está fazendo. Isso fornece uma localidade de função, que também permite separar melhor as preocupações e também obriga a pensar sobre ...
  2. Modularidade. Por que o analisador deve saber sobre toda a lista de veículos e todas as suas propriedades? Provavelmente não. Talvez seja necessário consultar se existe um ID do veículo ou se o veículo X tem a propriedade Y. Ótimo, isso significa que você pode escrever um serviço que faz isso e injetar apenas esse serviço no seu analisador. De repente, você acaba com um design que faz muito mais sentido, com cada pedaço de código lidando apenas com os dados relevantes para ele. O que nos leva a ...
  3. Testabilidade. Para um teste de unidade típico, você deseja configurar seu ambiente para muitos cenários diferentes, e a injeção facilita a implementação. Novamente, voltando ao exemplo do analisador, você realmente deseja criar sempre uma lista inteira de veículos completos para cada caso de teste que você escreve para o analisador? Ou você apenas criaria uma implementação simulada do objeto de serviço mencionado anteriormente, retornando um número variável de IDs? Eu sei qual eu escolheria.

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.

biziclop
fonte
+1 para uma resposta concisa focada em problemas de manutenção. Mais respostas P: SE devem ser como esta.
Dodgethesteamroller
1
Sua soma é tão boa. Tão bom. Se você acha que [padrão de design do mês] ou [palavra-chave do mês] resolverá magicamente seus problemas, você terá um mau momento.
precisa saber é o seguinte
@corsiKa Fiquei avesso ao DI (ou Spring, para ser mais preciso) por um longo tempo, porque não conseguia entender o motivo. Parecia um monte de problemas sem ganho tangível (isso estava de volta nos dias de configuração do XML). Eu gostaria de ter encontrado alguma documentação sobre o que DI realmente comprou para você naquela época. Mas eu não fiz, então eu mesmo tive que descobrir. Levou um longo tempo. :)
biziclop
3

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

gbjbaanb
fonte
"normalmente um objeto global é muito sustentável" - não se for mutável. Na minha experiência, ter tanto estado global mutável pode ser um pesadelo (embora haja maneiras de torná-lo suportável).
sleske 23/09/2015
3

Além das respostas que você já tem,

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.

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

Matemática
fonte
É verdade, mas, por exemplo, o analisador precisará saber tudo, pois pode ser recebido qualquer comando que possa fazer alterações em qualquer coisa.
vsz 22/09/15
1
"Antes de eu chegar à sua pergunta real" ... você vai responder a pergunta dele?
Gbjbaanb #
pergunta @gbjbaanb tem um monte de texto, por favor, permita-me algum tempo para lê-lo, adequadamente
Matemática