Estou com dificuldade para procurar recursos sobre por que devo usar a injeção de dependência . A maioria dos recursos que vejo explica que apenas passa uma instância de um objeto para outra instância de um objeto, mas por quê? Isso é apenas para arquitetura / código mais limpo ou isso afeta o desempenho como um todo?
Por que devo fazer o seguinte?
class Profile {
public function deactivateProfile(Setting $setting)
{
$setting->isActive = false;
}
}
Em vez do seguinte?
class Profile {
public function deactivateProfile()
{
$setting = new Setting();
$setting->isActive = false;
}
}
deactivateProfile
sugere-me que definirisActive
falso como sem se preocupar com o estado anterior é a abordagem correta aqui. Chamar o método inerentemente significa que você deseja defini- lo como inativo, não obtém seu status ativo (in) atual.Respostas:
A vantagem é que, sem injeção de dependência, sua classe Profile
Mas com injeção de dependência
Isso pode parecer (ou até mesmo) irrelevante nesse caso específico, mas imagine se não estamos falando de um objeto Settings, mas de um objeto DataStore, que pode ter implementações diferentes, uma que armazena dados em arquivos e outra que armazena em um banco de dados. E para testes automatizados, você também deseja uma implementação simulada. Agora, você realmente não deseja que a classe Profile codifique qual delas usa - e, o que é mais importante, realmente não deseja que a classe Profile conheça os caminhos do sistema de arquivos, conexões de banco de dados e senhas, portanto, a criação de objetos DataStore tem que acontecer em outro lugar.
fonte
This may seem (or even be) irrelevant in this particular case
Eu acho que é muito relevante, de fato. Como você obteria as configurações? Muitos sistemas que eu já vi terão um conjunto de configurações padrão codificadas e uma configuração voltada para o público; portanto, você precisará carregar os dois e substituir alguns valores pelas configurações públicas. Você pode até precisar de várias fontes de padrões. Talvez você esteja recebendo alguns do disco, outros da DB. Portanto, toda a lógica para obter configurações pode e não é trivial - definitivamente não é algo que consome código ou deva se preocupar.$setting = new Setting();
terrivelmente ineficiente. A injeção e a instanciação do objeto acontecem uma vez.A injeção de dependência torna seu código mais fácil de testar.
Aprendi isso em primeira mão quando fui encarregado de corrigir um bug difícil de detectar na integração do Magento ao PayPal.
Um problema surgia quando o PayPal informava ao Magento sobre uma falha no pagamento: o Magento não registrava a falha corretamente.
Testar uma possível correção "manualmente" seria muito tedioso: você precisaria, de alguma forma, acionar uma notificação do PayPal "Falha". Você precisaria enviar um cheque eletrônico, cancelá-lo e aguardar o erro. Isso significa mais de três dias para testar uma alteração no código de um caractere!
Felizmente, parece que os desenvolvedores principais do Magento que desenvolveram essa função tinham testes em mente e usaram um padrão de injeção de dependência para torná-la trivial. Isso nos permite verificar nosso trabalho com um caso de teste simples como este:
Tenho certeza de que o padrão de DI tem muitas outras vantagens, mas o aumento da testabilidade é o maior benefício em minha mente .
Se você estiver curioso sobre a solução para esse problema, consulte o repositório do GitHub aqui: https://github.com/bubbleupdev/BUCorefix_Paypalstatus
fonte
Por que (qual é o problema)?
O melhor mnemônico que encontrei para isso é " novo é cola ": toda vez que você usa
new
seu código, esse código é vinculado a essa implementação específica . Se você usar repetidamente new em construtores, criará uma cadeia de implementações específicas . E como você não pode "ter" uma instância de uma classe sem construí-la, não pode separar essa cadeia.Como exemplo, imagine que você está escrevendo um videogame de carro de corrida. Você começou com uma classe
Game
, que cria umRaceTrack
, que cria 8Cars
, e cada um cria umMotor
. Agora, se você quiser mais 4Cars
com uma aceleração diferente, precisará alterar todas as classes mencionadas , exceto talvezGame
.Código de limpeza
Sim .
No entanto, pode muito bem parecer menos claro nessa situação, porque é mais um exemplo de como fazê-lo . A vantagem real mostra apenas quando várias classes estão envolvidas e é mais difícil de demonstrar, mas imagine que você usaria o DI no exemplo anterior. O código que cria todas essas coisas pode se parecer com isso:
Agora é possível adicionar esses 4 carros diferentes adicionando estas linhas:
RaceTrack
,Game
,Car
, ouMotor
eram necessárias - o que significa que pode ser 100% de certeza que nós não introduzir quaisquer novos bugs lá!Considerações de desempenho
Não . Mas para ser completamente honesto com você, pode ser.
No entanto, mesmo nesse caso, é uma quantidade tão ridiculamente pequena que você não precisa se preocupar. Se em algum momento no futuro, você tem que escrever código para um tamagotchi com o equivalente a 5Mhz CPU e 2MB de RAM, então talvez você pode ter de se preocupar com isso.
Em 99,999% * dos casos, ele terá um desempenho melhor, porque você gastou menos tempo corrigindo bugs e mais tempo aprimorando seus algoritmos de recursos pesados.
* número completamente inventado
Informação adicionada: "codificado"
Não se engane, isso ainda é muito "codificado" - os números são escritos diretamente no código. Não codificado significa algo como armazenar esses valores em um arquivo de texto - por exemplo, no formato JSON - e depois lê-los nesse arquivo.
Para fazer isso, você deve adicionar código para ler um arquivo e depois analisar o JSON. Se você considerar o exemplo novamente; na versão não DI, a
Car
ou aMotor
agora precisa ler um arquivo. Isso não parece fazer muito sentido.Na versão DI, você o adicionaria ao código que configurava o jogo.
fonte
Eu sempre fiquei perplexo com a injeção de dependência. Parecia existir apenas dentro das esferas Java, mas essas esferas falavam disso com grande reverência. Esse é um dos grandes padrões que, segundo se diz, traz ordem ao caos. Mas os exemplos sempre foram complicados e artificiais, estabelecendo um não-problema e depois resolvendo resolvê-lo, tornando o código mais complicado.
Fazia mais sentido quando um colega Python me transmitiu essa sabedoria: está apenas passando argumentos para funções. É apenas um padrão; mais como um lembrete de que você pode pedir algo como argumento, mesmo que você possa ter fornecido um valor razoável.
Portanto, sua pergunta é aproximadamente equivalente a "por que minha função deveria receber argumentos?" e tem muitas das mesmas respostas, a saber: deixar o interlocutor tomar decisões .
Isso tem um custo, é claro, porque agora você está forçando o chamador a tomar alguma decisão (a menos que você opte pelo argumento), e a interface é um pouco mais complexa. Em troca, você ganha flexibilidade.
Então: Existe uma boa razão para você precisar especificamente usar esse
Setting
tipo / valor específico? Existe uma boa razão para que o código de chamada possa querer umSetting
tipo / valor diferente ? (Lembre-se, testes são código !)fonte
O exemplo que você dá não é injeção de dependência no sentido clássico. A injeção de dependência geralmente se refere à passagem de objetos em um construtor ou ao usar "injeção de setter" logo após a criação do objeto, para definir um valor em um campo em um objeto recém-criado.
Seu exemplo passa um objeto como argumento para um método de instância. Esse método de instância modifica um campo nesse objeto. Injeção de dependência? Não. Quebrando o encapsulamento e ocultando dados? Absolutamente!
Agora, se o código fosse assim:
Então eu diria que você está usando injeção de dependência. A diferença notável é um
Settings
objeto sendo passado para o construtor ou umProfile
objeto.Isso é útil se o objeto Settings for caro ou complexo de construir, ou Settings é uma interface ou classe abstrata em que existem várias implementações concretas para alterar o comportamento do tempo de execução.
Como você está acessando diretamente um campo no objeto Configurações em vez de chamar um método, não pode tirar proveito do polimorfismo, que é um dos benefícios da injeção de dependência.
Parece que as configurações de um perfil são específicas para esse perfil. Nesse caso, eu faria um dos seguintes:
Instanciar o objeto Configurações dentro do construtor Profile
Passe o objeto Configurações no construtor e copie sobre campos individuais que se aplicam ao perfil
Honestamente, passar o objeto Settings para
deactivateProfile
e modificar um campo interno do objeto Settings é um cheiro de código. O objeto Configurações deve ser o único a modificar seus campos internos.fonte
The example you give is not dependency injection in the classical sense.
- Isso não importa. As pessoas com OO têm objetos no cérebro, mas você ainda está entregando uma dependência a alguma coisa.Sei que estou chegando atrasado a esta festa, mas sinto que um ponto importante está sendo esquecido.
Você não deveria. Mas não porque a injeção de dependência é uma má ideia. É porque isso está fazendo errado.
Vamos analisar essas coisas usando código. Nós vamos fazer isso:
quando obtemos a mesma coisa:
Então, claro, parece uma perda de tempo. É quando você faz assim. Este não é o melhor uso da injeção de dependência. Não é nem o melhor uso de uma classe.
Agora, o que aconteceria se tivéssemos isso:
E agora o
application
é livre para ativar e desativar oprofile
sem precisar saber nada em particular sobre osetting
que está realmente mudando. Por que isso é bom? Caso você precise alterar a configuração. Aapplication
parede é protegida contra essas mudanças, assim você fica livre para enlouquecer em um espaço seguro, sem ter que assistir a tudo quebrar assim que tocar em algo.Isso segue a construção separada do princípio do comportamento . O padrão de DI aqui é simples. Crie tudo o que você precisa no nível mais baixo possível, conecte-os e inicie todo o comportamento passando com uma chamada.
O resultado é que você tem um lugar separado para decidir o que se conecta ao quê e um lugar diferente para gerenciar o que diz o que quer que seja.
Tente isso em algo que você deve manter ao longo do tempo e veja se isso não ajuda.
fonte
Como cliente, quando você contrata um mecânico para fazer alguma coisa no seu carro, você espera que esse mecânico construa um carro do zero apenas para depois trabalhar com ele? Não, você dá ao mecânico o carro em que deseja que ele trabalhe .
Como proprietário da garagem, quando você instrui um mecânico a fazer algo com um carro, você espera que o mecânico crie suas próprias peças de chave de fenda / chave / carro? Não, você fornece ao mecânico as peças / ferramentas que ele precisa usar
porque nós fazemos isso? Bem, pense sobre isso. Você é proprietário de uma garagem e quer contratar alguém para se tornar seu mecânico. Você os ensinará a ser um mecânico (= você escreverá o código).
O que será mais fácil:
Há enormes benefícios em não ter seu mecânico criando tudo do zero:
Se você contratar e treinar o mecânico especialista, vai acabar com um funcionário que custa mais, leva mais tempo para executar o que deveria ser um trabalho simples e precisará perpetuamente ser treinado novamente sempre que uma de suas muitas responsabilidades exigir ser atualizado.
A analogia do desenvolvimento é que, se você usar classes com dependências codificadas, terá dificuldades em manter as classes que precisarão de desenvolvimento / alterações contínuas sempre que uma nova versão do objeto (
Settings
no seu caso) for criada, e você terá que desenvolver lógica interna para que a classe possa criar diferentes tipos deSettings
objetos.Além disso, quem consumir sua classe agora também precisará solicitar à classe que crie o
Settings
objeto correto , em vez de simplesmente poder passar para a classe qualquerSettings
objeto que deseje passar. Isso significa desenvolvimento adicional para o consumidor descobrir como solicitar à classe que crie a ferramenta certa.Sim, a inversão de dependência exige um pouco mais de esforço para escrever, em vez de codificar a dependência. Sim, é chato ter que digitar mais.
Mas esse é o mesmo argumento que optar por codificar valores literais porque "declarar variáveis exige mais esforço". Tecnicamente correto, mas os profissionais superam os contras em várias ordens de magnitude .
O benefício da inversão de dependência não ocorre quando você cria a primeira versão do aplicativo. O benefício da inversão de dependência ocorre quando você precisa alterar ou estender essa versão inicial. E não se engane pensando que você acertará na primeira vez e não precisará estender / alterar o código. Você terá que mudar as coisas.
Isso não afeta o desempenho do aplicativo em tempo de execução. Mas isso afeta enormemente o tempo de desenvolvimento (e, portanto, o desempenho) do desenvolvedor .
fonte
Como todos os padrões, é muito válido perguntar "por quê" para evitar designs inchados.
Para injeção de dependência, isso é facilmente visto pensando nas duas facetas mais discutíveis do design de OOP ...
Baixo acoplamento
Acoplamento na programação de computadores :
Você deseja obter baixo acoplamento. Duas coisas fortemente acopladas significam que, se você mudar uma, provavelmente terá que mudar a outra. Erros ou restrições em um provavelmente irão induzir erros / restrições no outro; e assim por diante.
Uma classe que instancia objetos dos outros é um acoplamento muito forte, porque uma precisa saber sobre a existência da outra; ele precisa saber como instanciar (quais argumentos o construtor precisa) e esses argumentos precisam estar disponíveis ao chamar o construtor. Além disso, dependendo de a linguagem precisar de desconstrução explícita (C ++), isso introduzirá outras complicações. Se você introduzir novas classes (ou seja, uma
NextSettings
ou qualquer outra coisa), precisará voltar à classe original e adicionar mais chamadas aos construtores.Coesão alta
Coesão :
Este é o outro lado da moeda. Se você observar uma unidade de código (um método, uma classe, um pacote etc.), deseja que todo o código dessa unidade tenha o mínimo de responsabilidades possível.
Um exemplo básico para isso seria o padrão MVC: você separa claramente o modelo de domínio da visualização (GUI) e uma camada de controle que as cola.
Isso evita o inchaço do código, onde você obtém grandes pedaços que fazem muitas coisas diferentes; se você deseja alterar alguma parte dele, também precisa acompanhar todos os outros recursos, tentando evitar bugs, etc .; e você se programa rapidamente em um buraco onde é muito difícil sair.
Com a injeção de dependência, você delega a criação ou acompanha, bem, dependências para quaisquer classes (ou arquivos de configuração) que implementam sua DI. Outras classes não se importam muito com o que exatamente está acontecendo - elas estarão trabalhando com algumas interfaces genéricas e não têm idéia do que é a implementação real, o que significa que não são responsáveis pelas outras coisas.
fonte
Você deve usar técnicas para resolver os problemas que eles são bons em resolver quando tiver esses problemas. Inversão e injeção de dependência não são diferentes.
A inversão ou injeção de dependência é uma técnica que permite ao seu código decidir sobre qual implementação de um método é chamada em tempo de execução. Isso maximiza os benefícios da ligação tardia. A técnica é necessária quando o idioma não suporta a substituição do tempo de execução de funções que não são de instância. Por exemplo, Java não possui um mecanismo para substituir chamadas para um método estático por chamadas para uma implementação diferente; contraste com o Python, onde tudo o que é necessário para substituir a chamada de função é vincular o nome a uma função diferente (redesigne a variável que contém a função).
Por que queremos variar a implementação da função? Há duas razões principais:
Você também pode anotar a inversão dos contêineres de controle. Esta é uma técnica destinada a ajudá-lo a evitar árvores de construção enormes e emaranhadas que se parecem com este pseudocódigo:
Ele permite que você registre suas aulas e faça a construção para você:
Observe que é mais simples se as classes registradas puderem ser singletons sem estado .
Palavra de cautela
Observe que a inversão de dependência não deve ser sua resposta ideal para desacoplar a lógica. Procure oportunidades para usar a parametrização . Considere este método de pseudocódigo, por exemplo:
Poderíamos usar inversão de dependência para algumas partes deste método:
Mas não devemos, pelo menos não completamente. Observe que criamos uma classe stateful com
Querier
. Agora ele contém uma referência a algum objeto de conexão essencialmente global. Isso cria problemas como dificuldade em entender o estado geral do programa e como as diferentes classes se coordenam. Observe também que somos forçados a falsificar o consultante ou a conexão, se quisermos testar a lógica da média. Além disso, uma abordagem melhor seria aumentar a parametrização :E a conexão seria gerenciada em um nível ainda mais alto que é responsável pela operação como um todo e sabe o que fazer com essa saída.
Agora, podemos testar a lógica da média de maneira completamente independente da consulta e, além disso, podemos usá-la em uma variedade maior de situações. Podemos questionar se precisa mesmo os
MyQuerier
eAverager
objetos, e talvez a resposta é que não fazer se não temos a intenção de testar a unidadeStuffDoer
, e não o teste de unidadeStuffDoer
seria perfeitamente razoável, já que ele é tão intimamente ligado ao banco de dados. Pode fazer mais sentido deixar apenas os testes de integração cobri-lo. Nesse caso, poderíamos estar fazendo bemfetchAboveMin
eaverageData
em métodos estáticos.fonte