Entendendo a injeção de dependência

109

Estou lendo sobre injeção de dependência (DI). Para mim, é uma coisa muito complicada de fazer, pois eu estava lendo referenciando inversão de controle (IoC) e, por isso, senti que iria fazer uma jornada.

Este é o meu entendimento: em vez de criar um modelo na classe que também o consome, você passa (injeta) o modelo (já preenchido com propriedades interessantes) para onde é necessário (para uma nova classe que pode ser usada como parâmetro em o construtor).

Para mim, isso é apenas uma discussão. Devo ter saudades de entender o ponto? Talvez se torne mais óbvio com projetos maiores?

Meu entendimento é não-DI (usando pseudo-código):

public void Start()
{
    MyClass class = new MyClass();
}

...

public MyClass()
{
    this.MyInterface = new MyInterface(); 
}

E DI seria

public void Start()
{
    MyInterface myInterface = new MyInterface();
    MyClass class = new MyClass(myInterface);
}

...

public MyClass(MyInterface myInterface)
{
    this.MyInterface = myInterface; 
}

Alguém poderia lançar alguma luz, pois tenho certeza de que estou numa confusão aqui.

MyDaftQuestions
fonte
5
Tente ter 5 do MyInterface e MyClass AND e tenha várias classes formando uma cadeia de dependência. Torna-se uma dor de cabeça para configurar tudo.
Euphoric
159
" Para mim, isso é apenas uma discussão. Devo ter entendido errado o assunto? " Você entendeu. Isso é "injeção de dependência". Agora veja que outro jargão maluco você pode criar para conceitos simples, é divertido!
precisa
8
Não posso aprovar o comentário do senhor deputado Lippert o suficiente. Eu também achei um jargão confuso para algo que eu estava fazendo naturalmente de qualquer maneira.
Alex Humphrey
16
@ Ericricipper: Embora correto, acho que é um pouco redutor. Parâmetros como DI não é um conceito imediatamente direto para o programador imperativo / OO médio, que está acostumado a passar dados. O DI aqui está deixando você passar o comportamento , o que exige uma visão de mundo mais flexível do que um programador medíocre terá.
Phoshi
14
@Phoshi: Você faz uma boa observação, mas também mostra porque eu sou cético de que "injeção de dependência" é uma boa idéia em primeiro lugar. Se eu escrever uma classe que depende de sua correção e desempenho no comportamento de outra classe, a última coisa que quero fazer é tornar o usuário da classe responsável por construir e configurar corretamente a dependência! Esse é o meu trabalho. Toda vez que você permite que um consumidor injete uma dependência, você cria um ponto em que o consumidor pode fazer errado.
precisa

Respostas:

106

Bem, sim, você injeta suas dependências, através de um construtor ou através de propriedades.
Uma das razões para isso é não sobrecarregar o MyClass com os detalhes de como uma instância do MyInterface precisa ser construída. MyInterface pode ser algo que possui uma lista inteira de dependências por si só, e o código do MyClass se tornaria feio muito rápido se você instanciasse todas as dependências do MyInterface dentro do MyClass.

Outra razão é para testar.
Se você possui uma dependência de uma interface do leitor de arquivos e a injeta por meio de um construtor, digamos, ConsumerClass, isso significa que durante o teste, você pode passar uma implementação em memória do leitor de arquivos para o ConsumerClass, evitando a necessidade de faça E / S durante o teste.

Stefan Billiet
fonte
13
Isso é ótimo, bem explicado. Aceite um +1 imaginário, pois meu representante ainda não o permitirá!
MyDaftQuestions
11
O cenário de construção complicado é um bom candidato para o uso do padrão Factory. Dessa forma, a fábrica assume a responsabilidade de construir o objeto, para que a própria classe não precise e você se atenha ao princípio de responsabilidade única.
Matt Gibson
1
@MattGibson good point.
Stefan Billiet 13/03
2
+1 para testes, o DI bem estruturado ajudará a acelerar os testes e a fazer testes de unidade contidos na classe que você está testando.
Umur Kontacı
52

Como a DI pode ser implementada depende muito do idioma usado.

Aqui está um exemplo simples não-DI:

class Foo {
    private Bar bar;
    private Qux qux;

    public Foo() {
        bar = new Bar();
        qux = new Qux();
    }
}

Isso é péssimo, por exemplo, quando para um teste eu quero usar um objeto simulado bar. Portanto, podemos tornar isso mais flexível e permitir que as instâncias sejam transmitidas pelo construtor:

class Foo {
    private Bar bar;
    private Qux qux;

    public Foo(Bar bar, Qux qux) {
        this.bar = bar;
        this.qux = qux;
    }
}

// in production:
new Foo(new Bar(), new Qux());
// in test:
new Foo(new BarMock(), new Qux());

Essa já é a forma mais simples de injeção de dependência. Mas isso ainda é péssimo porque tudo precisa ser feito manualmente (também porque o chamador pode manter uma referência a nossos objetos internos e, assim, invalidar nosso estado).

Podemos introduzir mais abstração usando fábricas:

  • Uma opção é Foogerar uma fábrica abstrata

    interface FooFactory {
        public Foo makeFoo();
    }
    
    class ProductionFooFactory implements FooFactory {
        public Foo makeFoo() { return new Foo(new Bar(), new Baz()) }
    }
    
    class TestFooFactory implements FooFactory {
        public Foo makeFoo() { return new Foo(new BarMock(), new Baz()) }
    }
    
    FooFactory fac = ...; // depends on test or production
    Foo foo = fac.makeFoo();
  • Outra opção é passar uma fábrica para o construtor:

    interface DependencyManager {
        public Bar makeBar();
        public Qux makeQux();
    }
    class ProductionDM implements DependencyManager {
        public Bar makeBar() { return new Bar() }
        public Qux makeQux() { return new Qux() }
    }
    class TestDM implements DependencyManager {
        public Bar makeBar() { return new BarMock() }
        public Qux makeQux() { return new Qux() }
    }
    
    class Foo {
        private Bar bar;
        private Qux qux;
    
        public Foo(DependencyManager dm) {
            bar = dm.makeBar();
            qux = dm.makeQux();
        }
    }

Os problemas restantes com isso são que precisamos escrever uma nova DependencyManagersubclasse para cada configuração e que o número de dependências que podem ser gerenciadas é bastante restrito (cada nova dependência precisa de um novo método na interface).

Com recursos como reflexão e carregamento dinâmico de classe, podemos contornar isso. Mas isso depende muito do idioma usado. No Perl, as classes podem ser referenciadas por seus nomes, e eu poderia fazer

package Foo {
    use signatures;

    sub new($class, $dm) {
        return bless {
            bar => $dm->{bar}->new,
            qux => $dm->{qux}->new,
        } => $class;
    }
}

my $prod = { bar => 'My::Bar', qux => 'My::Qux' };
my $test = { bar => 'BarMock', qux => 'QuxMock' };
$test->{bar} = 'OtherBarMock';  # change conf at runtime

my $foo = Foo->new(rand > 0.5 ? $prod : $test);

Em linguagens como Java, eu poderia fazer com que o gerenciador de dependências se comportasse de maneira semelhante a Map<Class, Object>:

Bar bar = dm.make(Bar.class);

Para qual classe real a Bar.classsolução é resolvida pode ser configurada em tempo de execução, por exemplo, mantendo uma Map<Class, Class>interface que mapeia as implementações.

Map<Class, Class> dependencies = ...;

public <T> T make(Class<T> c) throws ... {
    // plus a lot more error checking...
    return dependencies.get(c).newInstance();
}

Ainda há um elemento manual envolvido na escrita do construtor. Mas podemos tornar o construtor completamente desnecessário, por exemplo, acionando o DI por meio de anotações:

class Foo {
    @Inject(Bar.class)
    private Bar bar;

    @Inject(Qux.class)
    private Qux qux;

    ...
}

dm.make(Foo.class);  // takes care of initializing "bar" and "qux"

Aqui está um exemplo de implementação de uma pequena estrutura DI (e muito restrita): http://ideone.com/b2ubuF , embora essa implementação seja completamente inutilizável para objetos imutáveis ​​(essa implementação ingênua não pode aceitar parâmetros para o construtor).

amon
fonte
7
Isso é ótimo, com ótimos exemplos, embora me leve algumas vezes a ler para digerir tudo, mas obrigado por dedicar tanto tempo.
MyDaftQuestions
33
É divertido como cada lado simplificação é mais complexa
Kromster
48

Nossos amigos no Stack Overflow têm uma boa resposta para isso . O meu favorito é a segunda resposta, incluindo a citação:

"Injeção de dependência" é um termo de 25 dólares para um conceito de 5 centavos. (...) Injeção de dependência significa atribuir a um objeto suas variáveis ​​de instância. (...)

do blog de James Shore . Obviamente, existem versões / padrões mais complexos em camadas, mas é o suficiente para entender basicamente o que está acontecendo. Em vez de um objeto criar suas próprias variáveis ​​de instância, elas são passadas de fora.

user967543
fonte
10
Eu tinha lido isso ... e até agora, não fazia sentido ... Agora, faz sentido. Dependência não é realmente um conceito tão grande para entender (independentemente de quão poderoso seja), mas tem um nome grande e muito ruído na Internet associado a ele!
MyDaftQuestions
18

Digamos que você esteja fazendo o design de interiores da sua sala de estar: instale um lustre sofisticado no teto e conecte uma luminária de pé elegante. Um ano depois, sua adorável esposa decide que gostaria que o quarto fosse um pouco menos formal. Qual luminária será mais fácil de trocar?

A idéia por trás do DI é usar a abordagem "plug-in" em qualquer lugar. Isso pode não parecer grande coisa se você mora em uma cabana simples, sem drywall e com todos os fios elétricos expostos. (Você é um trabalhador braçal - pode mudar qualquer coisa!) E da mesma forma, em um aplicativo pequeno e simples, o DI pode adicionar mais complicações do que vale a pena.

Mas para uma aplicação grande e complexa, o DI é indispensável para manutenção a longo prazo. Ele permite que você "desconecte" módulos inteiros do seu código (o back-end do banco de dados, por exemplo) e os troque por módulos diferentes que realizam a mesma coisa, mas que o fazem de uma maneira totalmente diferente (um sistema de armazenamento em nuvem, por exemplo).

BTW, uma discussão sobre DI seria incompleta sem uma recomendação de Injection de Dependência no .NET , por Mark Seeman. Se você conhece o .NET e pretende se envolver no desenvolvimento de SW em larga escala, essa é uma leitura essencial. Ele explica o conceito muito melhor do que eu.

Deixe-me deixar uma última característica essencial do DI que seu exemplo de código ignora. O DI permite que você aproveite o vasto potencial de flexibilidade que está encapsulado no ditado " Programa para interfaces ". Para modificar um pouco o seu exemplo:

public void Main()
{
    ILightFixture fixture = new ClassyChandelier();
    MyRoom room = new MyRoom (fixture);
}
...
public MyRoom(ILightFixture fixture)
{
    this.MyLightFixture = fixture ; 
}

Mas AGORA, como MyRoomé projetado para a interface ILightFixture , eu posso facilmente entrar no próximo ano e alterar uma linha na função Principal ("injetar" uma "dependência" diferente)) e obter instantaneamente novas funcionalidades no MyRoom (sem precisar reconstrua ou reimplemente o MyRoom, se houver uma biblioteca separada, assumindo, é claro, que todas as suas implementações do ILightFixture foram projetadas com a Substituição Liskov em mente). Tudo, com apenas uma mudança simples:

public void Main()
{
    ILightFixture fixture = new MuchLessFormalLightFixture();
    MyRoom room = new MyRoom (fixture);
}

Além disso, agora posso testar a unidade MyRoom(você está testando a unidade, certo?) E usar uma luminária "simulada", que me permite testar MyRoomtotalmente independente daClassyChandelier.

Existem muitas outras vantagens, é claro, mas essas são as que venderam a ideia para mim.

kmote
fonte
Queria agradecer, isso foi útil para mim, mas eles não querem que você faça isso, mas use comentários para sugerir melhorias; portanto, se você quiser, poderá adicionar uma das outras vantagens mencionadas. Mas, caso contrário, obrigado, isso foi útil para mim.
Steve Steve
2
Também estou interessado no livro que você referenciou, mas acho alarmante que exista um livro de 584 páginas sobre esse assunto.
Steve Steve
4

A injeção de dependência está apenas passando um parâmetro, mas isso ainda leva a alguns problemas, e o nome deve se concentrar um pouco no por que a diferença entre criar um objeto e passar um é importante.

É importante porque (obviamente) qualquer objeto de tempo A chama o tipo B, A depende de como B funciona. O que significa que, se A tomar a decisão específica de qual tipo concreto de B ele usará, será perdida muita flexibilidade na maneira como A pode ser usado por classes externas, sem modificar A.

Menciono isso porque você fala sobre "perder o objetivo" da injeção de dependência, e esse é o principal motivo pelo qual as pessoas gostam de fazer isso. Pode significar apenas passar um parâmetro, mas se você fizer isso, pode ser importante.

Além disso, algumas das dificuldades na implementação efetiva de DI aparecem melhor em grandes projetos. Você geralmente moverá a decisão real de quais classes concretas usar até o topo, para que seu método inicial possa parecer:

public void Start()
{
    MySubSubInterfaceA mySubSubInterfaceA = new mySubSubInterfaceA();
    MySubSubInterfaceB mySubSubInterfaceB = new mySubSubInterfaceB();     
    MySubInterface mySubInterface = new MySubInterface(mySubSubInterfaceA,mySubSubInterfaceB);
    MyInterface myInterface = new MyInterface(MySubInterface);
    MyClass class = new MyClass(myInterface);
}

mas com outras 400 linhas desse tipo de código fascinante. Se você pensa em manter isso com o tempo, o apelo dos contêineres DI se torna mais aparente.

Imagine também uma classe que, digamos, implementa IDisposable. Se as classes que o utilizam o obtêm por injeção, em vez de criá-lo, como eles sabem quando chamar Dispose ()? (Esse é outro problema que um contêiner de DI lida com.)

Portanto, com certeza, a injeção de dependência está apenas passando um parâmetro, mas, na verdade, quando as pessoas dizem que a injeção de dependência significa "passar um parâmetro para seu objeto versus a alternativa de fazer com que seu objeto instancia algo" - e lidar com todos os benefícios das desvantagens de tal abordagem, possivelmente com a ajuda de um contêiner DI.

psr
fonte
São muitos subs e interfaces! Você pretende criar uma interface em vez de uma classe que implementa a interface? Parece errado.
JBRWilkinson
@JBRWilkinson Quero dizer uma classe que implementa uma interface. Eu deveria deixar isso mais claro. No entanto, o ponto é que um aplicativo grande terá classes com dependências que, por sua vez, dependem que, por sua vez, dependem ... Configurar tudo manualmente no método Main é o resultado de muita injeção de construtor.
Psr
4

No nível da turma, é fácil.

'Injeção de dependência' simplesmente responde à pergunta "como vou encontrar meus colaboradores" com "eles são empurrados contra você - você não precisa ir buscá-los". (É semelhante a - mas não é o mesmo que - 'Inversão de controle', onde a pergunta "como ordenarei minhas operações sobre minhas entradas?" Tem uma resposta semelhante).

O único benefício que seus colaboradores pressionam é que permite que o código do cliente use sua classe para compor um gráfico de objeto que atenda às suas necessidades atuais ... você não determinou arbitrariamente a forma e a mutabilidade do gráfico ao decidir em particular os tipos concretos e o ciclo de vida de seus colaboradores.

(Todos esses outros benefícios, de testabilidade, de acoplamento flexível, etc., decorrem amplamente do uso de interfaces e não tanto da dependência-injeção-ness, embora o DI promova naturalmente o uso de interfaces).

Vale a pena notar que, se você evitar instanciar seus próprios colaboradores, sua classe deverá, portanto, obter seus colaboradores de um construtor, uma propriedade ou um argumento de método (essa última opção geralmente é ignorada, a propósito ... nem sempre faz sentido que os colaboradores de uma classe façam parte de seu 'estado').

E isso é uma coisa boa.

No nível do aplicativo ...

Tanto para a visão por classe das coisas. Digamos que você tenha várias classes que seguem a regra "não instancia seus próprios colaboradores" e deseja fazer uma aplicação a partir delas. A coisa mais simples a fazer é usar um bom código antigo (uma ferramenta realmente útil para invocar construtores, propriedades e métodos!) Para compor o gráfico de objetos que você deseja e lançar alguma entrada nele. (Sim, alguns desses objetos em seu gráfico serão fábricas de objetos, que foram passadas como colaboradores para outros objetos de longa duração no gráfico, prontos para o serviço ... você não pode pré-construir cada objeto! )

... sua necessidade de 'flexivelmente' configurar o gráfico de objetos do seu aplicativo ...

Dependendo dos seus outros objetivos (não relacionados ao código), convém dar ao usuário final algum controle sobre o gráfico de objetos assim implantado. Isso o leva na direção de um esquema de configuração, de um tipo ou de outro, seja um arquivo de texto de seu próprio design, com alguns pares de nome / valor, um arquivo XML, uma DSL personalizada, uma linguagem declarativa de descrição gráfica, como YAML, uma linguagem de script imperativa, como JavaScript, ou outra coisa apropriada para a tarefa em questão. O que for necessário para compor um gráfico de objeto válido, de maneira a atender às necessidades de seus usuários.

... pode ser uma força de projeto significativa.

Na circunstância mais extrema desse tipo, você pode optar por adotar uma abordagem muito geral e fornecer aos usuários finais um mecanismo geral para 'conectar' o gráfico de objetos de sua escolha e até permitir que eles forneçam realizações concretas de interfaces para o tempo de execução! (Sua documentação é uma jóia brilhante, seus usuários são muito inteligentes, familiarizados com pelo menos o contorno geral do gráfico de objetos do seu aplicativo, mas não têm um compilador à mão). Teoricamente, esse cenário pode ocorrer em algumas situações da empresa.

Nesse caso, você provavelmente possui uma linguagem declarativa que permite que seus usuários expressem qualquer tipo, a composição de um gráfico de objetos desses tipos e uma paleta de interfaces com as quais o mítico usuário final pode misturar e combinar. Para reduzir a carga cognitiva de seus usuários, você prefere uma abordagem de 'configuração por convenção', para que eles só precisem intervir e substituir o fragmento de objeto-gráfico-interesse, em vez de lutar com a coisa toda.

Seu pobre idiota!

Como você não gostava de escrever tudo isso sozinho (mas sério, confira uma ligação YAML para o seu idioma), você está usando algum tipo de estrutura DI.

Dependendo da maturidade dessa estrutura, você pode não ter a opção de usar injeção de construtor, mesmo quando faz sentido (os colaboradores não mudam durante a vida útil de um objeto), forçando-o a usar a Injeção de Setter (mesmo quando os colaboradores não mudam durante o tempo de vida de um objeto, e mesmo quando não há realmente uma razão lógica pela qual todas as implementações concretas de uma interface devem ter colaboradores de um tipo específico). Nesse caso, você está atualmente em um inferno de fortes acoplamentos, apesar de ter diligentemente 'usado interfaces' em toda a sua base de código - horror!

Felizmente, você usou uma estrutura de DI que oferece a opção de injeção de construtor, e seus usuários ficam um pouco irritados com você por não gastar mais tempo pensando nas coisas específicas que eles precisavam para configurar e fornecendo a eles uma interface do usuário mais adequada para a tarefa à mão. (Apesar de ser justo, você provavelmente tentou pensar em uma maneira, mas o JavaEE decepcionou e você teve que recorrer a esse truque horrível).

Nota de inicialização

Em nenhum momento você está usando o Google Guice, que fornece ao codificador uma maneira de dispensar a tarefa de compor um gráfico de objetos com código ... escrevendo código. Argh!

David Bullock
fonte
0

Muitas pessoas disseram aqui que o DI é um mecanismo para terceirizar a inicialização de uma variável de instância.

Para exemplos óbvios e simples, você realmente não precisa de "injeção de dependência". Você pode fazer isso com um simples parâmetro passando para o construtor, conforme observado em questão.

No entanto, acho que um mecanismo dedicado de "injeção de dependência" é útil quando você fala sobre recursos no nível do aplicativo, onde o chamador e o destinatário não sabem nada sobre esses recursos (e não querem saber!).

Por exemplo: conexão com o banco de dados, contexto de segurança, recurso de log, arquivo de configuração etc.

Além disso, em geral todo singleton é um bom candidato para DI. Quanto mais você tiver e usar, mais chances precisará de DI.

Para esses tipos de recursos, é bastante claro que não faz sentido movê-los por meio de listas de parâmetros e, portanto, o DI foi inventado.

Última observação, você também precisa de um contêiner DI, que fará a injeção real ... (novamente, para liberar o chamador e o receptor dos detalhes da implementação).

gamliela
fonte
-2

Se você passar um tipo de referência abstrata, o código de chamada poderá passar qualquer objeto que estenda / implemente o tipo abstrato. Portanto, no futuro, a classe que usa o objeto pode usar um novo objeto que foi criado sem nunca modificar seu código.

DavidB
fonte
-4

Essa é outra opção, além da resposta de amon

Use Construtores:

classe Foo {
    bar privado;
    Qux privado qux;

    privado Foo () {
    }

    public Foo (construtor Builder) {
        this.bar = builder.bar;
        this.qux = builder.qux;
    }

    Construtor de classe estática pública {
        bar privado;
        Qux privado qux;
        public Buider setBar (barra de barras) {
            this.bar = bar;
            devolva isso;
        }
        public Builder setQux (Qux qux) {
            this.qux = qux;
            devolva isso;
        }
        public Foo build () {
            retornar novo Foo (this);
        }
    }
}

// em produção:
Foo foo = novo Foo.Builder ()
    .setBar (new Bar ())
    .setQux (novo Qux ())
    .Construir();

// no teste:
Foo testFoo = novo Foo.Builder ()
    .setBar (novo BarMock ())
    .setQux (novo Qux ())
    .Construir();

É isso que eu uso para dependências. Eu não uso nenhuma estrutura DI. Eles atrapalham.

user3155341
fonte
4
Isso não acrescenta muito às outras respostas de um ano atrás e não oferece explicação sobre por que um construtor pode ajudar o solicitante a entender a injeção de dependência.
@ Snowman É outra opção no lugar de DI. Me desculpe, você não vê dessa maneira. Obrigado pelo voto negativo.
User3155341
1
o solicitante queria entender se o DI é realmente tão simples quanto passar um parâmetro, ele não estava pedindo alternativas ao DI.
1
Os exemplos do OP são muito bons. Seus exemplos fornecem mais complexidade para algo que é uma ideia simples. Eles não ilustram a questão da questão.
Adam Zuckerman
O construtor DI está passando um parâmetro. Exceto que você está passando um objeto de interface em vez de um objeto de classe concreto. É essa 'dependência' de uma implementação concreta que é resolvida via DI. O construtor não sabe que tipo está sendo passado, nem se importa, apenas sabe que está recebendo um tipo que implementa uma interface. Eu sugiro o PluralSight, ele tem um ótimo curso para iniciantes em DI / COI.
Hardgraf 08/07