O que é injeção de construtor?


47

Eu estive analisando os termos injeção de construtor e injeção de dependência enquanto examinava artigos sobre padrões de design (localizador de serviços).

Quando pesquisei no Google sobre injeção de construtores, obtive resultados pouco claros, o que me levou a fazer o check-in aqui.

O que é injeção de construtor? Esse é um tipo específico de injeção de dependência? Um exemplo canônico seria uma grande ajuda!

Editar

Revisitando essas perguntas depois de um intervalo de uma semana, posso ver como eu estava perdida ... Caso alguém mais apareça aqui, atualizarei o corpo da pergunta com um pouco de aprendizado meu. Por favor, sinta-se livre para comentar / corrigir. Injeção de construtor e injeção de propriedade são dois tipos de injeção de dependência.


5
O primeiro hit no Google explica claramente ... misko.hevery.com/2009/02/19/…
user281377

Respostas:


95

Não sou especialista, mas acho que posso ajudar. E sim, é um tipo específico de injeção de dependência.

Isenção de responsabilidade: quase tudo isso foi "roubado" do Ninject Wiki

Vamos examinar a idéia de injeção de dependência, percorrendo um exemplo simples. Digamos que você esteja escrevendo o próximo jogo de grande sucesso, onde nobres guerreiros lutam por grande glória. Primeiro, precisaremos de uma arma adequada para armar nossos guerreiros.

class Sword 
{
    public void Hit(string target)
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

Então, vamos criar uma classe para representar nossos próprios guerreiros. Para atacar seus inimigos, o guerreiro precisará de um método Attack (). Quando esse método é chamado, ele deve usar sua espada para golpear seu oponente.

class Samurai
{
    readonly Sword sword;
    public Samurai() 
    {
        this.sword = new Sword();
    }

    public void Attack(string target)
    {
        this.sword.Hit(target);
    }
}

Agora, podemos criar nosso Samurai e lutar!

class Program
{
    public static void Main() 
    {
        var warrior = new Samurai();
        warrior.Attack("the evildoers");
    }
}

Como você pode imaginar, isso imprimirá Chopped the evildoers limpo ao meio no console. Isso funciona muito bem, mas e se quiséssemos armar nosso Samurai com outra arma? Como o Sword é criado dentro do construtor da classe Samurai, precisamos modificar a implementação da classe para fazer essa alteração.

Quando uma classe depende de uma dependência concreta, diz-se que ela está fortemente acoplada a essa classe . Neste exemplo, a classe Samurai está fortemente acoplada à classe Sword. Quando as classes são fortemente acopladas, elas não podem ser trocadas sem alterar sua implementação. Para evitar o acoplamento de classes, podemos usar interfaces para fornecer um nível de indireção. Vamos criar uma interface para representar uma arma em nosso jogo.

interface IWeapon
{
    void Hit(string target);
}

Então, nossa classe Sword pode implementar esta interface:

class Sword : IWeapon
{
    public void Hit(string target) 
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

E podemos alterar nossa classe Samurai:

class Samurai
{
    readonly IWeapon weapon;
    public Samurai() 
    {
        this.weapon = new Sword();
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

Agora nosso Samurai pode estar armado com armas diferentes. Mas espere! A espada ainda é criada dentro do construtor do Samurai. Como ainda precisamos alterar a implementação do Samurai para dar ao nosso guerreiro outra arma, o Samurai ainda está fortemente acoplado ao Sword.

Felizmente, existe uma solução fácil. Em vez de criar a espada de dentro do construtor de Samurai, podemos expô-la como um parâmetro do construtor. Também conhecida como Injeção de Construtor.

class Samurai
{
    readonly IWeapon weapon;
    public Samurai(IWeapon weapon) 
    {
        this.weapon = weapon;
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

Como Giorgio apontou, também há injeção de propriedade. Isso seria algo como:

class Samurai
{
    IWeapon weapon;

    public Samurai() { }


    public void SetWeapon(IWeapon weapon)
    {
        this.weapon = weapon;
    }

    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }

}

Espero que isto ajude.


8
Adorei esse! :) Na verdade, parece uma história! Apenas curioso. Se você quisesse armar o mesmo Samurai com várias armas (sequencialmente), a injeção de propriedade seria a melhor maneira, certo?
TheSilverBullet

Sim, você poderia fazer isso. Na verdade, acho que seria melhor passar o IWeapon como parâmetro para o método Attack. Como "Public void Attack (IWeapon with, string target)". E para evitar código duplicado, eu tornaria o método existente "public void Attack (string target)" intacto e chamaria "this.Attack (this.weapon, target)".
Luiz Angelo

E então combinado com fábricas, você pode obter métodos como CreateSwordSamurai ()! Belo exemplo.
Geerten

2
Ótimo exemplo! Então, claro ...
Serge van den Oever

@Luiz Angelo. Desculpe, não tenho certeza se entendi o seu comentário: "Na verdade, acho melhor você passar o IWeapon como um parâmetro para o método Attack. Como ..". Você quer dizer sobrecarregar o método Attack na classe Samurai?
Pap

3

Vou tentar o máximo possível para tornar isso muito elementar. Dessa forma, você pode desenvolver o conceito e criar suas próprias soluções ou idéias complexas.

Agora imagine que temos uma igreja chamada Jubileu, que tem filiais em todo o mundo. Nosso objetivo é simplesmente obter um item de cada filial. Aqui estaria a solução com o DI;

1) Crie uma interface IJubilee:

public interface IJubilee
{
    string GetItem(string userInput);
}

2) Crie uma classe JubileeDI que levaria a interface IJubilee como construtor e retornaria um item:

public class JubileeDI
{
    readonly IJubilee jubilee;

    public JubileeDI(IJubilee jubilee)
    {
        this.jubilee = jubilee;
    }

    public string GetItem(string userInput)
    {
        return this.jubilee.GetItem(userInput);
    }
}

3) Agora crie três braches do Jubileu, a saber, JubileeGT, JubileeHOG, JubileeCOV, que todos DEVEM herdar a interface do IJubilee. Por diversão, faça com que um deles implemente seu método como virtual:

public class JubileeGT : IJubilee
{
    public virtual string GetItem(string userInput)
    {
        return string.Format("For JubileeGT, you entered {0}", userInput);
    }
}

public class JubileeHOG : IJubilee
{
    public string GetItem(string userInput)
    {
        return string.Format("For JubileeHOG, you entered {0}", userInput);
    }
}

public class JubileeCOV : IJubilee
{
    public string GetItem(string userInput)
    {
        return string.Format("For JubileCOV, you entered {0}", userInput);
    }
}

public class JubileeGTBranchA : JubileeGT
{
    public override string GetItem(string userInput)
    {
        return string.Format("For JubileeGT branch A, you entered {0}", userInput);
    }
}

4) É isso aí! Agora vamos ver o DI em ação:

        JubileeDI jCOV = new JubileeDI(new JubileeCOV());
        JubileeDI jHOG = new JubileeDI(new JubileeHOG());
        JubileeDI jGT = new JubileeDI(new JubileeGT());
        JubileeDI jGTA = new JubileeDI(new JubileeGTBranchA());

        var item = jCOV.GetItem("Give me some money!");
        var item2 = jHOG.GetItem("Give me a check!");
        var item3 = jGT.GetItem("I need to be fed!!!");
        var item4 = jGTA.GetItem("Thank you!");

Para cada instância da classe de contêiner, passamos uma nova instância da classe que exigimos, o que permite um acoplamento flexível.

Espero que isso explique o conceito em poucas palavras.


2

Suponha que você tenha uma classe em Acada instância, que precisa de uma instância de outra classe B.

class A
{
   B b;
}

Injeção de dependência significa que a referência a Bé definida pelo objeto que gerencia a instância A(em vez de ter a classe Agerenciando a referência Bdiretamente).

Injeção de construtor significa que a referência a Bé passada como um parâmetro para o construtor Ae definido no construtor:

class A
{
    B b;

    A(B b)
    {
        this.b = b;
    }
}

Uma alternativa é usar um método setter (ou uma propriedade) para definir a referência B. Neste caso, o objeto que gerencia uma instância ade Adeve primeiro chamar o construtor Ae depois chamar o método setter para definir a variável de membro A.bantes aé usado. O método de injecção último é necessário quando os objectos conter referências cíclicas para o outro, por exemplo, se um exemplo bde Bque é definido em um exemplo ade Acontem uma referência de volta para a.

** EDIT **

Aqui estão mais alguns detalhes para abordar o comentário.

1. A classe A gerencia a instância B

class A
{
    B b;

    A()
    {
        b = new B();
    }
 }

2. A instância B é definida no construtor

class A
{
    B b;

    A(B b)
    {
        this.b = b;
    }
}

class M
{
    ...
    B b = new B();
    A a = new A(b);
}

3. Instância B é configurada usando o método setter

class A
{
    B b;

    A()
    {
    }

    void setB(B b)
    {
        this.b = b;
    }
}

class M
{
    ...
    B b = new B();
    A a = new A();

    a.setB(b);
}

... em vez de ter a classe A gerenciando a referência a B diretamente . O que isto significa? Como você faria isso em código? (Desculpem a minha ignorância.)
TheSilverBullet

1
O exemplo do samurai é muito melhor. Vamos parar de usar letras para variáveis, por favor ...
stuartdotnet 22/04

@stuartdotnet: Eu não discuto sobre o exemplo do samurai ser melhor ou pior, mas por que parar de usar variáveis ​​de uma letra? Se as variáveis ​​não têm significado específico, mas são apenas espaços reservados, as variáveis ​​de letra única são muito mais compactas e objetivas. Usar nomes mais longos como itemAou objectAapenas para aumentar o nome nem sempre é melhor.
Giorgio

1
Então você perdeu meu argumento - usar nomes não descritivos é difícil de ler, seguir e entender, e não é uma boa maneira de explicar um argumento
stuartdotnet

@ stuartdotnet: Acho que não entendi bem o seu argumento: você afirma que os nomes descritivos são sempre melhores e tornam o código sempre mais legível e autoexplicativo.
Giorgio
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.