¿Qué es la inyección de constructor?


47

He estado mirando los términos inyección de constructor e inyección de dependencia mientras revisaba artículos sobre patrones de diseño (Localizador de servicios).

Cuando busqué en Google la inyección del constructor, obtuve resultados poco claros, lo que me llevó a verificar aquí.

¿Qué es la inyección de constructor? ¿Es este un tipo específico de inyección de dependencia? ¡Un ejemplo canónico sería de gran ayuda!

Editar

Revisando estas preguntas después de un intervalo de una semana, puedo ver lo perdido que estaba ... En caso de que alguien más aparezca aquí, actualizaré el cuerpo de preguntas con un poco de aprendizaje mío. Por favor, siéntase libre de comentar / corregir. La inyección de constructor y la inyección de propiedades son dos tipos de inyección de dependencia.


55
El primer hit en google lo explica claramente ... misko.hevery.com/2009/02/19/…
user281377

Respuestas:


95

No soy un experto, pero creo que puedo ayudar. Y sí, es un tipo específico de inyección de dependencia.

Descargo de responsabilidad: Casi todo esto fue "robado" de Ninject Wiki

Examinemos la idea de la inyección de dependencia siguiendo un ejemplo simple. Digamos que estás escribiendo el próximo juego de gran éxito, donde los guerreros nobles luchan por la gran gloria. Primero, necesitaremos un arma adecuada para armar a nuestros guerreros.

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

Entonces, creemos una clase para representar a nuestros propios guerreros. Para atacar a sus enemigos, el guerrero necesitará un método Attack (). Cuando se llama a este método, debe usar su Espada para golpear a su oponente.

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

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

¡Ahora podemos crear nuestro Samurai y luchar!

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

Como puede imaginar, esto imprimirá Chopped the evildoers por la mitad en la consola. Esto funciona bien, pero ¿y si quisiéramos armar a nuestro Samurai con otra arma? Dado que la Espada se crea dentro del constructor de la clase Samurai, tenemos que modificar la implementación de la clase para hacer este cambio.

Cuando una clase depende de una dependencia concreta, se dice que está estrechamente vinculada a esa clase . En este ejemplo, la clase Samurai está estrechamente unida a la clase Espada. Cuando las clases están estrechamente acopladas, no pueden intercambiarse sin alterar su implementación. Para evitar un acoplamiento estrecho de clases, podemos usar interfaces para proporcionar un nivel de indirección. Creemos una interfaz para representar un arma en nuestro juego.

interface IWeapon
{
    void Hit(string target);
}

Entonces, nuestra clase Sword puede implementar esta interfaz:

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

Y podemos alterar nuestra clase de Samurai:

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

Ahora nuestro Samurai puede estar armado con diferentes armas. ¡Pero espera! La espada todavía se crea dentro del constructor de Samurai. Como todavía necesitamos alterar la implementación de Samurai para darle a nuestro guerrero otra arma, Samurai todavía está estrechamente unido a Sword.

Afortunadamente, hay una solución fácil. En lugar de crear la Espada desde dentro del constructor de Samurai, podemos exponerla como un parámetro del constructor. También conocido como inyección de constructor.

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

Como señaló Giorgio, también hay inyección de propiedades. Eso sería 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 esto ayude.


8
Me encantó este! :) En realidad se lee como una historia! Sólo curioso. Si quisieras armar al mismo Samurai con múltiples armas (secuencialmente), la inyección de propiedades sería la mejor manera, ¿verdad?
TheSilverBullet

Sí, puedes hacer eso. En realidad, creo que sería mejor que pases el IWeapon como parámetro del método de ataque. Como "Public void Attack (IWeapon with, string target)". Y para evitar el código duplicado, dejaría intacto el método existente "Public void Attack (string target)" y lo llamaría "this.Attack (this.weapon, target)".
Luiz Angelo

¡Y luego, combinado con fábricas, puede obtener métodos como CreateSwordSamurai ()! Buen ejemplo
Geerten

2
Gran ejemplo! Tan claro ...
Serge van den Oever

@Luiz Angelo. Lo siento, no estoy seguro de haber entendido tu comentario: "En realidad, creo que sería mejor que pases el IWeapon como parámetro del método de ataque. Me gusta ...". ¿Te refieres a sobrecargar el método de ataque en la clase Samurai?
Pap

3

Haré todo lo posible para que esto sea muy elemental. De esa manera, puede construir sobre el concepto y crear sus propias soluciones o ideas complejas.

Ahora imagine que tenemos una iglesia llamada Jubilee, que tiene sucursales en todo el mundo. Nuestro objetivo es simplemente obtener un artículo de cada rama. Aquí estaría la solución con DI;

1) Crear una interfaz IJubilee:

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

2) Cree una clase JubileeDI que tome la interfaz IJubilee como constructor y devuelva un elemento:

public class JubileeDI
{
    readonly IJubilee jubilee;

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

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

3) Ahora cree tres braches de Jubilee, a saber, JubileeGT, JubileeHOG, JubileeCOV, que DEBEN heredar la interfaz IJubilee. Por diversión, haz que uno de ellos implemente su 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) ¡Eso es! Ahora veamos DI en acción:

        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 instancia de la clase contenedor, pasamos una nueva instancia de la clase que requerimos, lo que permite un acoplamiento flexible.

Espero que esto explique el concepto en pocas palabras.


2

Suponga que tiene una clase Acada instancia de la cual necesita una instancia de otra clase B.

class A
{
   B b;
}

La inyección de dependencia significa que la referencia a la Bestablece el objeto que administra la instancia de A(en lugar de que la clase Aadministre la referencia Bdirectamente).

La inyección de constructor significa que la referencia a Bse pasa como parámetro al constructor de Ay se establece en el constructor:

class A
{
    B b;

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

Una alternativa es utilizar un método de establecimiento (o una propiedad) para establecer la referencia B. En este caso, el objeto que gestiona una instancia ade Aprimero debe llamar al constructor Ay luego al método setter para establecer la variable miembro A.bantes de que ase use. El último método de inyección es necesario cuando los objetos contienen referencias cíclicas entre sí, por ejemplo, si una instancia bde Beso se establece en una instancia ade Acontiene una referencia inversa a a.

** EDITAR **

Aquí hay algunos detalles más para abordar el comentario.

1. La clase A gestiona la instancia B

class A
{
    B b;

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

2. La instancia B se establece en el constructor

class A
{
    B b;

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

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

3. La instancia B se establece usando el 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);
}

... en lugar de que la clase A gestione la referencia a B directamente . ¿Qué significa esto? ¿Cómo lo harías en código? (Perdón por mi ignorancia.)
TheSilverBullet

1
El ejemplo del samurai es mucho mejor. Dejemos de usar letras para las variables, por favor ...
stuartdotnet

@stuartdotnet: No discuto sobre el ejemplo de samurai que es mejor o peor, pero ¿por qué dejar de usar variables de una letra? Si las variables no tienen un significado particular pero son solo marcadores de posición, las variables de una letra son mucho más compactas y precisas. Usar nombres más largos como itemAo objectAsimplemente para hacer que el nombre sea más largo no siempre es mejor.
Giorgio

1
Entonces te has perdido mi punto: usar nombres no descriptos es difícil de leer, seguir y comprender, y no es una buena manera de explicar un punto
stuartdotnet

@stuartdotnet: No creo haber perdido su punto: Usted afirma que los nombres descriptivos siempre son mejores y hacen que el código sea siempre más legible y autoexplicativo.
Giorgio
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.