¿Inyección de dependencias a través de constructores o establecedores de propiedades?


151

Estoy refactorizando una clase y agregando una nueva dependencia. La clase actualmente está tomando sus dependencias existentes en el constructor. Entonces, por coherencia, agrego el parámetro al constructor.
Por supuesto, hay algunas subclases y aún más para las pruebas unitarias, así que ahora estoy jugando el juego de alterar a todos los constructores para que coincidan, y está tomando años.
Me hace pensar que usar propiedades con setters es una mejor manera de obtener dependencias. No creo que las dependencias inyectadas sean parte de la interfaz para construir una instancia de una clase. Agrega una dependencia y ahora todos sus usuarios (subclases y cualquiera que lo instale directamente) de repente lo sepan. Eso se siente como un descanso de encapsulación.

Este no parece ser el patrón con el código existente aquí, así que estoy buscando averiguar cuál es el consenso general, los pros y los contras de los constructores versus las propiedades. ¿Es mejor usar establecedores de propiedades?

Respuestas:


126

Bueno, eso depende :-).

Si la clase no puede hacer su trabajo sin la dependencia, agréguela al constructor. La clase necesita la nueva dependencia, por lo que desea que su cambio rompa las cosas. Además, crear una clase que no está completamente inicializada ("construcción en dos pasos") es un antipatrón (en mi humilde opinión).

Si la clase puede funcionar sin la dependencia, un setter está bien.


11
Creo que en muchos casos es preferible usar el patrón de objeto nulo y seguir exigiendo las referencias en el constructor. Esto evita toda comprobación nula y el aumento de la complejidad ciclomática.
Mark Lindell

3
@ Mark: Buen punto. Sin embargo, la pregunta era sobre agregar una dependencia a una clase existente. Luego, mantener un constructor sin argumentos permite la compatibilidad con versiones anteriores.
sleske

¿Qué pasa cuando la dependencia es necesaria para funcionar, pero una inyección predeterminada de esa dependencia generalmente será suficiente? Entonces, ¿debería esa dependencia ser "reemplazable" por propiedad o por una sobrecarga del constructor?
Patrick Szalapski el

@Patrick: Por "la clase no puede hacer su trabajo sin la dependencia", quise decir que no hay un valor predeterminado razonable (por ejemplo, la clase requiere una conexión de base de datos). En su situación, ambos funcionarían. Por lo general, todavía optaría por el enfoque de constructor, porque reduce la complejidad (¿y si, por ejemplo, se llama dos veces al colocador?
sleske


21

Se supone que los usuarios de una clase deben conocer las dependencias de una clase determinada. Si tuviera una clase que, por ejemplo, se conectara a una base de datos, y no proporcionara un medio para inyectar una dependencia de la capa de persistencia, un usuario nunca sabría que una conexión a la base de datos tendría que estar disponible. Sin embargo, si modifico el constructor, les hago saber a los usuarios que hay una dependencia en la capa de persistencia.

Además, para evitar tener que modificar cada uso del viejo constructor, simplemente aplique el encadenamiento del constructor como un puente temporal entre el viejo y el nuevo constructor.

public class ClassExample
{
    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo)
        : this (dependnecyOne, dependencyTwo, new DependnecyThreeConcreteImpl())
    { }

    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo, IDependencyThree dependencyThree)
    {
        // Set the properties here.
    }
}

Uno de los puntos de inyección de dependencias es revelar qué dependencias tiene la clase. Si la clase tiene demasiadas dependencias, entonces puede ser hora de que tenga lugar alguna refactorización: ¿Todos los métodos de la clase utilizan todas las dependencias? Si no, entonces ese es un buen punto de partida para ver dónde podría dividirse la clase.


Por supuesto, el encadenamiento del constructor solo funciona si hay un valor predeterminado razonable para el nuevo parámetro. Pero por lo demás no se puede evitar romper las cosas de todos modos ...
sleske

Por lo general, usaría lo que estaba usando en el método antes de la inyección de dependencia como parámetro predeterminado. Idealmente, esto haría que la adición del nuevo constructor sea una refactorización limpia, ya que el comportamiento de la clase no cambiaría.
Doctor Blue

1
Tomo su punto sobre las dependencias de gestión de recursos como las conexiones de bases de datos. Creo que el problema en mi caso es que la clase a la que estoy agregando una dependencia tiene varias subclases. En un mundo contenedor IOC donde la propiedad sería establecida por el contenedor, el uso del setter al menos aliviaría la presión sobre la duplicación de la interfaz del constructor entre todas las subclases.
Niall Connaughton

17

Por supuesto, poner el constructor significa que puede validar todo a la vez. Si asigna cosas en campos de solo lectura, tiene algunas garantías sobre las dependencias de su objeto desde el momento de la construcción.

Es un verdadero dolor agregar nuevas dependencias, pero al menos de esta manera el compilador sigue quejándose hasta que sea correcto. Lo cual es bueno, creo.


Más uno para esto. Además, esto reduce extremadamente el peligro de dependencias circulares ...
gilgamash

11

Si tiene una gran cantidad de dependencias opcionales (que ya es un olor), entonces probablemente la mejor opción sea la inyección de setter. Sin embargo, la inyección de constructor revela mejor sus dependencias.


11

El enfoque general preferido es utilizar la inyección del constructor tanto como sea posible.

La inyección de constructor establece exactamente cuáles son las dependencias requeridas para que el objeto funcione correctamente: nada es más molesto que renovar un objeto y hacer que se bloquee al llamar a un método porque no se establece alguna dependencia. El objeto devuelto por un constructor debe estar en estado de funcionamiento.

Intente tener un solo constructor, mantiene el diseño simple y evita la ambigüedad (si no es para los humanos, para el contenedor DI).

Puede usar la inyección de propiedad cuando tiene lo que Mark Seemann llama un valor predeterminado local en su libro "Inyección de dependencias en .NET": la dependencia es opcional porque puede proporcionar una implementación de trabajo fina pero desea permitir que la persona que llama especifique una diferente si necesario.

(Antigua respuesta a continuación)


Creo que la inyección del constructor es mejor si la inyección es obligatoria. Si esto agrega demasiados constructores, considere usar fábricas en lugar de constructores.

La inyección de setter es buena si la inyección es opcional, o si desea cambiarla hasta la mitad. Generalmente no me gustan los setters, pero es cuestión de gustos.


Yo diría que, por lo general, cambiar la inyección a la mitad es un mal estilo (porque está agregando estado oculto a su objeto). Pero no hay una regla sin excepción, por supuesto ...
sleske

Sí, por eso dije que no me gustaba mucho el setter ... Me gusta el enfoque del constructor, ya que no se puede cambiar.
Philippe

"Si esto agrega demasiados constructores, considere usar fábricas en lugar de constructores". Básicamente difiere cualquier excepción de tiempo de ejecución e incluso podría equivocarse y terminar atascado con la implementación de un localizador de servicios.
MeTitus

@Marco esa es mi respuesta anterior y tienes razón. Si hay muchos constructores, diría que la clase hace demasiadas cosas :-) O considere una fábrica abstracta.
Philippe

7

Es en gran medida una cuestión de gusto personal. Personalmente, tiendo a preferir la inyección de setter, porque creo que le da más flexibilidad en la forma en que puede sustituir implementaciones en tiempo de ejecución. Además, los constructores con muchos argumentos no están limpios en mi opinión, y los argumentos proporcionados en un constructor deberían limitarse a argumentos no opcionales.

Mientras la interfaz de clases (API) sea clara en lo que necesita para realizar su tarea, eres bueno.


especifique por qué está votando en contra
nkr1pt

3
Sí, los constructores con muchos argumentos son malos. Es por eso que refactoriza clases con muchos parámetros de constructor :-).
sleske 01 de

10
@ nkr1pt: La mayoría de las personas (incluido yo) están de acuerdo en que la inyección de setter es mala si le permite crear una clase que falla en tiempo de ejecución si la inyección no se realiza. Por lo tanto, creo que alguien se opuso a su declaración de que era un gusto personal.
sleske 01 de

7

Personalmente, prefiero el "patrón" Extraer y anular sobre las inyecciones de dependencias en el constructor, en gran parte por la razón descrita en su pregunta. Puede establecer las propiedades como virtualy luego anular la implementación en una clase comprobable derivada.


1
Creo que el nombre oficial del patrón es "Método de plantilla".
davidjnelson

6

Prefiero la inyección del constructor porque ayuda a "hacer cumplir" los requisitos de dependencia de una clase. Si está en el centro, un consumidor tiene que configurar los objetos para que la aplicación se compile. Si usa la inyección de setter, es posible que no sepan que tienen un problema hasta el tiempo de ejecución, y dependiendo del objeto, puede ser tarde en el tiempo de ejecución.

Todavía uso la inyección de setter de vez en cuando cuando el objeto inyectado puede necesitar un montón de trabajo en sí mismo, como la inicialización.


6

Prefiero la inyección del constructor, porque esto parece más lógico. Es como decir que mi clase requiere estas dependencias para hacer su trabajo. Si es una dependencia opcional, las propiedades parecen razonables.

También utilizo la inyección de propiedades para configurar cosas a las que el contenedor no tiene referencias, como una vista ASP.NET en un presentador creado usando el contenedor.

No creo que rompa la encapsulación. El funcionamiento interno debe permanecer interno y las dependencias se ocupan de una preocupación diferente.


Gracias por tu respuesta. Ciertamente parece que el constructor es la respuesta popular. Sin embargo, creo que rompe la encapsulación de alguna manera. Inyección previa a la dependencia, la clase declararía e instanciaría cualquier tipo concreto que necesitara para hacer su trabajo. Con DI, las subclases (y cualquier instanciador manual) ahora saben qué herramientas está usando una clase base. Agrega una nueva dependencia y ahora tiene que encadenar la instancia desde todas las subclases, incluso si no necesitan usar la dependencia ellos mismos.
Niall Connaughton

¡Acabo de escribir una buena respuesta larga y la perdí debido a una excepción en este sitio! :( En resumen, la clase base generalmente se usa para reutilizar la lógica. Esta lógica podría entrar fácilmente en la subclase ... así que podría pensar en la clase base y la subclase = una preocupación, que depende de múltiples objetos externos, que . hacer diferentes trabajos El hecho de que tiene dependencias, no significa que usted necesita para exponer algo que le ha mantenido previamente privado.
David Kiff

3

Una opción que podría valer la pena considerar es componer dependencias múltiples complejas a partir de dependencias simples simples. Es decir, definir clases adicionales para dependencias compuestas. Esto facilita un poco la inyección del constructor WRT, menos parámetros por llamada, y al mismo tiempo mantiene la necesidad de suministrar todas las dependencias para instanciar.

Por supuesto, tiene más sentido si hay algún tipo de agrupación lógica de dependencias, por lo que el compuesto es más que un agregado arbitrario, y tiene más sentido si hay múltiples dependientes para una sola dependencia compuesta, pero el "patrón" del bloque de parámetros tiene ha existido durante mucho tiempo, y la mayoría de los que he visto han sido bastante arbitrarios.

Personalmente, sin embargo, soy más fanático de usar métodos / establecedores de propiedades para especificar dependencias, opciones, etc. Los nombres de las llamadas ayudan a describir lo que está sucediendo. Sin embargo, es una buena idea proporcionar ejemplos de fragmentos de "así es cómo configurarlo" y asegurarse de que la clase dependiente realice suficientes comprobaciones de errores. Es posible que desee utilizar un modelo de estado finito para la configuración.


3

Recientemente me encontré con una situación en la que tenía múltiples dependencias en una clase, pero solo una de las dependencias iba a cambiar necesariamente en cada implementación. Dado que el acceso a los datos y las dependencias de registro de errores probablemente solo se cambiarían con fines de prueba, agregué parámetros opcionales para esas dependencias y proporcioné implementaciones predeterminadas de esas dependencias en mi código de constructor. De esta manera, la clase mantiene su comportamiento predeterminado a menos que sea anulado por el consumidor de la clase.

El uso de parámetros opcionales solo se puede lograr en marcos que los admitan, como .NET 4 (tanto para C # como para VB.NET, aunque VB.NET siempre los ha tenido). Por supuesto, puede lograr una funcionalidad similar simplemente usando una propiedad que puede ser reasignada por el consumidor de su clase, pero no obtiene la ventaja de la inmutabilidad proporcionada al tener un objeto de interfaz privado asignado a un parámetro del constructor.

Dicho todo esto, si está introduciendo una nueva dependencia que debe proporcionar cada consumidor, tendrá que refactorizar su constructor y todo el código que consume su clase. Mis sugerencias anteriores realmente solo se aplican si tiene el lujo de poder proporcionar una implementación predeterminada para todo su código actual, pero aún brinda la capacidad de anular la implementación predeterminada si es necesario.


1

Esta es una publicación antigua, pero si se necesita en el futuro, tal vez sea útil:

https://github.com/omegamit6zeichen/prinject

Tuve una idea similar y se me ocurrió este marco. Probablemente esté lejos de estar completo, pero es una idea de un marco centrado en la inyección de propiedades


0

Depende de cómo quieras implementarlo. Prefiero la inyección del constructor donde siento que los valores que intervienen en la implementación no cambian a menudo. Por ejemplo: si la estrategia de empresa es ir con el servidor Oracle, configuraré mis valores de fuente de datos para un bean que logre conexiones a través de la inyección del constructor. De lo contrario, si mi aplicación es un producto y existe la posibilidad de que se pueda conectar a cualquier base de datos del cliente, implementaría dicha configuración de base de datos y la implementación de múltiples marcas a través de la inyección de setter. Acabo de tomar un ejemplo, pero hay mejores formas de implementar los escenarios que mencioné anteriormente.


1
Incluso cuando codifico internamente, siempre codifico desde el punto de vista de que soy un contratista independiente que desarrolla código destinado a ser redistribuible. Supongo que será de código abierto. De esta manera, me aseguro de que el código sea modular y conectable y siga los principios de SOLID.
Fred

0

La inyección del constructor revela explícitamente las dependencias, lo que hace que el código sea más legible y menos propenso a errores de tiempo de ejecución no controlados si se verifican los argumentos en el constructor, pero realmente se reduce a una opinión personal, y cuanto más use DI, más tienden a balancearse de un lado a otro según el proyecto. Personalmente, tengo problemas con los olores de código como constructores con una larga lista de argumentos, y creo que el consumidor de un objeto debe conocer las dependencias para poder usar el objeto de todos modos, por lo que esto justifica el uso de la inyección de propiedades. No me gusta la naturaleza implícita de la inyección de propiedades, pero la encuentro más elegante, lo que da como resultado un código más limpio. Pero, por otro lado, la inyección del constructor ofrece un mayor grado de encapsulación,

Elija la inyección por constructor o por propiedad sabiamente según su escenario específico. Y no sienta que tiene que usar DI solo porque parece necesario y evitará malos olores de diseño y código. A veces no vale la pena usar un patrón si el esfuerzo y la complejidad superan el beneficio. Mantenlo simple.

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.