¿Deben inyectarse dependencias en el ctor o por método?

16

Considerar:

public class CtorInjectionExample
{
    public CtorInjectionExample(ISomeRepository SomeRepositoryIn, IOtherRepository OtherRepositoryIn)
    {
        this._someRepository = SomeRepositoryIn;
        this._otherRepository = OtherRepositoryIn;
    }

    public void SomeMethod()
    {
        //use this._someRepository
    }

    public void OtherMethod()
    {
        //use this._otherRepository
    }
}

en contra:

public class MethodInjectionExample
{
    public MethodInjectionExample()
    {
    }

    public void SomeMethod(ISomeRepository SomeRepositoryIn)
    {
        //use SomeRepositoryIn
    }

    public void OtherMethod(IOtherRepository OtherRepositoryIn)
    {
        //use OtherRepositoryIn
    }
}

Si bien la inyección de Ctor dificulta la extensión (cualquier código que llame al ctor necesitará actualizarse cuando se agreguen nuevas dependencias), y la inyección a nivel de método parece permanecer más encapsulada de la dependencia a nivel de clase y no puedo encontrar ningún otro argumento a favor / en contra de estos enfoques .

¿Existe un enfoque definitivo para tomar para inyección?

(Nota: he buscado información sobre esto y he tratado de hacer que esta pregunta sea objetiva).

StuperUser
fuente
44
Si sus clases son coherentes, muchos métodos diferentes estarían usando los mismos campos. Esto debería darte la respuesta que buscas ...
Oded
@Oded Good point, la cohesión influiría fuertemente en la decisión. Si una clase es, por ejemplo, colecciones de métodos auxiliares que tienen dependencias diferentes (aparentemente no cohesivas, pero lógicamente similares) y otra es altamente cohesiva, cada una debería usar el enfoque más beneficioso. Sin embargo, esto causaría inconsistencia en una solución.
StuperUser
Relevante para los ejemplos que he dado: en.wikipedia.org/wiki/False_dilemma
StuperUser

Respuestas:

3

Depende, si el objeto inyectado es utilizado por más de un método en la clase, e inyectarlo en cada método no tiene sentido, debe resolver las dependencias para cada llamada al método, y si la dependencia es utilizada solo por un método inyectado en el constructor no es bueno, pero dado que está asignando recursos que nunca podrían usarse.

nohros
fuente
15

Bien, en primer lugar, tratemos con "Cualquier código que llame al ctor deberá actualizarse cuando se agreguen nuevas dependencias"; Para ser claros, si está haciendo una inyección de dependencia y tiene algún código que llama a new () en un objeto con dependencias, lo está haciendo mal .

Su contenedor DI debería ser capaz de inyectar todas las dependencias relevantes, por lo que no debe preocuparse por cambiar la firma del constructor, de modo que ese argumento realmente no sea válido.

En cuanto a la idea de inyección por método versus por clase, hay dos problemas principales con la inyección por método.

Un problema es que los métodos de su clase deben compartir dependencias, es una forma de ayudar a asegurarse de que sus clases estén separadas de manera efectiva, si ve una clase con una gran cantidad de dependencias (probablemente más de 4-5), entonces esa clase es un candidato principal para refactorizar en dos clases.

El siguiente problema es que para "inyectar" las dependencias, por método, tendría que pasarlas a la llamada al método. Esto significa que tendrá que resolver las dependencias antes de la llamada al método, por lo que probablemente terminará con un montón de código como este:

var someDependency = ServiceLocator.Resolve<ISomeDependency>();
var something = classBeingInjected.DoStuff(someDependency);

Ahora, supongamos que va a llamar a este método en 10 lugares alrededor de su aplicación: tendrá 10 de estos fragmentos. Luego, digamos que necesita agregar otra dependencia a DoStuff (): tendrá que cambiar ese fragmento 10 veces (o envolverlo en un método, en cuyo caso solo está replicando el comportamiento de DI manualmente, lo cual es un desperdicio de tiempo).

Entonces, lo que básicamente ha hecho allí es hacer que sus clases que utilizan DI sean conscientes de su propio contenedor de DI, lo cual es una idea fundamentalmente mala , ya que rápidamente conduce a un diseño torpe que es difícil de mantener.

Compare eso con la inyección del constructor; En la inyección de constructor, no está atado a un contenedor DI particular, y nunca es directamente responsable de cumplir con las dependencias de sus clases, por lo que el mantenimiento es bastante libre de dolor de cabeza.

Me parece que está tratando de aplicar IoC a una clase que contiene un montón de métodos auxiliares no relacionados, mientras que es mejor dividir la clase auxiliar en una serie de clases de servicio en función del uso, luego usar el auxiliar para delegar el llamadas. Esto todavía no es un gran enfoque (ayudante clasificado con métodos que hacen algo más complejo que solo lidiar con los argumentos que se les pasan, generalmente son solo clases de servicio mal escritas), pero al menos mantendrá su diseño un poco más limpio .

(Nota: he hecho el enfoque que estás sugiriendo antes, y fue una muy mala idea que no haya repetido desde entonces. Resulté que estaba tratando de separar las clases que realmente no necesitaban separarse, y terminé con un conjunto de interfaces donde cada llamada al método requería una selección casi fija de otras interfaces. Fue una pesadilla mantener).

Ed James
fuente
1
"if you're doing dependency injection and you have any code calling new() on an object with dependencies, you're doing it wrong."Si está probando un método en una clase, debe crear una instancia o ¿usa DI para crear una instancia de su Código bajo prueba?
StuperUser
@StuperUser Unit testing es un escenario ligeramente diferente, mi comentario solo se aplica realmente al código de producción. Como va a hacer dependencias simuladas (o de código auxiliar) para sus pruebas a través de un marco de simulación, cada uno con un comportamiento preconfigurado y un conjunto de supuestos y aserciones, deberá inyectarlos a mano.
Ed James
1
Ese es el código del que estoy hablando cuando digo que "any code calling the ctor will need updating"mi código de producción no llama a los ctors. Por estas razones.
StuperUser
2
@StuperUser Lo suficientemente justo, pero aún así va a llamar a los métodos con las dependencias requeridas, lo que significa que el resto de mi respuesta sigue en pie. Para ser honesto, estoy de acuerdo sobre el problema con la inyección del constructor y forzar todas las dependencias, desde una perspectiva de prueba de unidad. Tiendo a usar la inyección de propiedades, ya que esto solo le permite inyectar las dependencias que espera necesitar para una prueba, ¡lo cual es básicamente otro conjunto de llamadas implícitas Assert.WasNotCalled sin tener que escribir ningún código! : D
Ed James
3

Hay varios tipos de inversión de control , y su pregunta propone solo dos (también puede usar la fábrica para inyectar dependencia).

La respuesta es: depende de la necesidad. A veces es mejor usar un tipo y otro diferente.

Personalmente, me gustan los inyectores de constructor, ya que el constructor está allí para inicializar completamente el objeto. Tener que llamar a un setter más adelante hace que el objeto no esté completamente construido.

BЈовић
fuente
3

Te perdiste el que al menos para mí es el más flexible: la inyección de propiedades. Uso Castle / Windsor y todo lo que necesito hacer es agregar una nueva propiedad automática a mi clase y obtengo el objeto inyectado sin ningún problema y sin romper ninguna interfaz.

Otávio Décio
fuente
Estoy acostumbrado a las aplicaciones web y si estas clases están en la capa de dominio, llamada por la interfaz de usuario, que ya tiene sus dependencias resueltas de manera similar a la inyección de propiedades. Me pregunto cómo lidiar con las clases a las que puedo pasar los objetos inyectados.
StuperUser
También me gusta la inyección de propiedades, simplemente para que pueda seguir usando parámetros de constructor normales con un contenedor DI, diferenciando automáticamente entre las dependencias resueltas y no resueltas. Sin embargo, como el constructor y la inyección de propiedades son funcionalmente idénticos para este escenario, elegí no mencionarlo;)
Ed James
La inyección de propiedades obliga a cada objeto a ser mutable y crea instancias en un estado no válido. ¿Por qué sería eso preferible?
Chris Pitman
2
@Chris En realidad, en condiciones normales, el constructor y la inyección de propiedades actúan de forma casi idéntica, y el objeto se inyecta completamente durante la resolución. El único momento en que podría considerarse "inválido" es durante la prueba de la unidad, cuando no va a utilizar el contenedor DI para instanciar la implementación. Vea mi comentario sobre mi respuesta para ver por qué esto es realmente beneficioso. Aprecio que la mutabilidad podría ser un problema, pero en realidad solo vas a hacer referencia a clases resueltas a través de sus interfaces, lo que no expondrá las dependencias para editar.
Ed James