2009-10-01 16 views
136

Estoy refactorizando una clase y agregando una nueva dependencia a ella. La clase está tomando sus dependencias existentes en el constructor. Entonces, por coherencia, agrego el parámetro al constructor.
Por supuesto, hay algunas subclases e incluso más para las pruebas unitarias, así que ahora estoy jugando el juego de alterar todos los constructores para que coincida, y está tardando años.
Me hace pensar que usar propiedades con setters es una mejor forma 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 cualquier instancia de usted directamente) de repente lo saben. Eso se siente como una ruptura de la encapsulación.¿Inyección de dependencia mediante constructores o definidores de propiedad?

Esto no parece ser el patrón con el código existente aquí, entonces estoy buscando averiguar cuál es el consenso general, pros y contras de constructores versus propiedades. ¿Está usando mejor los setters de propiedades?

Respuesta

117

Bueno, 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, la creación de una clase que no está totalmente inicializada ("construcción en dos pasos") es un anti-patrón (en mi humilde opinión).

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

+9

Creo que en muchos casos es preferible usar el patrón Objeto nulo y mantener las referencias en el constructor. Esto evita todas las comprobaciones nulas y la mayor complejidad ciclomática. –

+3

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

+0

¿Qué ocurre cuando se necesita la dependencia para funcionar, pero una inyección predeterminada de esa dependencia suele ser suficiente. Entonces, ¿esa dependencia debería ser "reemplazable" por la propiedad o por una sobrecarga del constructor? –

6

Es en gran medida una cuestión de gusto personal. Personalmente tiendo a preferir la inyección 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 son limpios en mi opinión, y los argumentos provistos en un constructor deben estar limitados a argumentos no opcionales.

Siempre que la interfaz de clases (API) sea clara en lo que necesita para realizar su tarea, está bien.

+0

por favor especifique por qué está bajando la votación? – nkr1pt

+3

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

+10

@ nkr1pt: la mayoría de las personas (yo incluido) acepta que la inyección setter es mala si le permite crear una clase que falle en el tiempo de ejecución si la inyección no se realiza. Creo que alguien por lo tanto se opuso a su declaración de que es un gusto personal. – sleske

6

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

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

11

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

17

Por supuesto, poner en el constructor significa que puede validar todo de una vez. Si asigna cosas en campos de solo lectura, entonces 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 algo bueno, creo.

+0

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

6

I perfer 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 uso la inyección de propiedades para configurar cosas a las que el contenedor no tiene referencias, como una vista de ASP.NET en un presentador creado utilizando el contenedor.

No creo que rompa la encapsulación. El funcionamiento interno debe permanecer interno y las dependencias deben lidiar con una preocupación diferente.

+0

Gracias por su respuesta. Sin duda, parece que el constructor es la respuesta popular. Sin embargo, creo que rompe la encapsulación de alguna manera. En la inyección de dependencia, la clase declararía y crearía una instancia de los tipos concretos que necesitaba para hacer su trabajo. Con DI, las subclases (y cualquier instanciador manual) ahora saben qué herramientas está usando una clase base. Agregas una nueva dependencia y ahora tienes que encadenar la instancia a través de todas las subclases, incluso si no necesitan usar la dependencia ellos mismos. –

+0

Acabo de escribir una buena larga ¡¡¡Responda y lo perdió debido a una excepción en este sitio !! :(En resumen, la clase base se usa generalmente para reutilizar la lógica. Esta lógica podría entrar fácilmente en la subclase ... por lo 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 tenga dependencias, no significa que necesite exponer cualquier cosa que haya mantenido previamente en privado. –

7

Personalmente prefiero Extraer e invalidar "patrón" sobre la inyección de dependencias en el constructor, en gran parte por el motivo que se indica en su pregunta. Puede establecer las propiedades como virtual y 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

11

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

La inyección del constructor indica exactamente cuáles son las dependencias necesarias para que el objeto funcione correctamente; nada es más molesto que reactivar un objeto y tenerlo bloqueado al invocar un método porque no se establece alguna dependencia. El objeto devuelto por un constructor debe estar en un estado de trabajo.

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

Puede utilizar la inyección de la propiedad cuando se tiene lo que Mark Seemann llama un predeterminada local en su libro "inyección de dependencias en .NET": la dependencia es opcional, ya que puede proporcionar una implementación de trabajo bien, pero quieren permitir que la persona que llama para especificar uno diferente si es necesario.

(ex respuesta a continuación)


creo que la inyección de constructor son mejores si la inyección es obligatorio. Si esto agrega demasiados constructores, considere usar fábricas en lugar de constructores.

La inyección setter es agradable si la inyección es opcional, o si desea cambiarla a la mitad. En general, no me gustan los setters, pero es una cuestión de gusto.

+0

Yo diría que generalmente cambia la inyección a mitad de camino es un mal estilo (porque estás agregando un estado oculto a tu objeto). Pero ninguna regla sin excepción, por supuesto ... – sleske

+0

Sí, por eso dije que no me gustaba demasiado la setter ... Me gusta el constructor enfoque como no se puede cambiar. – Philippe

+0

"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 puede obtener errores y finalizar hasta quedar atrapado con la implementación de un localizador de servicios. – Marco

19

Los usuarios de una clase son supuesto para 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 capa de persistencia, un usuario nunca sabría que una conexión a la base de datos debería estar disponible. Sin embargo, si modifico el constructor, les digo a los usuarios que existe una dependencia en la capa de persistencia.

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

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 dependencia es revelar qué dependencias tiene la clase. Si la clase tiene demasiadas dependencias, entonces puede ser el momento para que se lleve a cabo alguna refactorización: ¿Todos los métodos de la clase usan todas las dependencias? Si no, entonces ese es un buen punto de partida para ver dónde se podría dividir la clase.

+0

Por supuesto, el encadenamiento de constructores solo funciona si hay un valor predeterminado razonable para el nuevo parámetro. Pero de lo contrario no puede evitar romper cosas de todos modos ... – sleske

+0

Normalmente, utilizaría lo que estaba usando en el método antes de la inyección de dependencia como el parámetro predeterminado. Idealmente, esto haría que el nuevo constructor agregue una refactorización limpia, ya que el comportamiento de la clase no cambiará. – Scott

+1

Me refiero a las dependencias de administración de recursos, como las conexiones de bases de datos. Creo que el problema en mi caso es que la clase a la que agrego una dependencia tiene varias subclases. En un mundo contenedor de IOC donde la propiedad sería establecida por el contenedor, usar el setter al menos aliviaría la presión en la duplicación de la interfaz del constructor entre todas las subclases. –

3

Una opción que podría valer la pena considerar es la composición de múltiples dependencias complejas a partir de dependencias simples simples. Es decir, defina clases adicionales para dependencias compuestas.Esto hace que las cosas sean un poco más sencillas. Inyección de constructores de WRT, menos parámetros por llamada, mientras se mantiene la necesidad de proporcionar todas las dependencias para crear instancias.

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 bloque de parámetros El "patrón" ha existido por mucho tiempo, y la mayoría de los que he visto han sido bastante arbitrarios.

Personalmente, soy más partidario de usar métodos/definidores 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 esto-es-cómo-hacer-configurar-fragmentos, y asegurarse de que la clase dependiente haga suficientes comprobaciones de errores. Es posible que desee utilizar un modelo de estado finito para la configuración.

3

Hace poco ran into a situation donde tenía varias dependencias en una clase, pero solo una de las dependencias iba a cambiar necesariamente en cada implementación. Dado que las dependencias de acceso a datos y registro de errores probablemente solo se cambiarían para 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 realizar 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 pueda 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á presentando una nueva dependencia que debe ser proporcionada por cada consumidor, tendrá que refactorizar su constructor y todo el código que los consumidores de su clase. Mis sugerencias anteriores solo se aplican si tiene el lujo de poder proporcionar una implementación predeterminada para todo su código actual, pero aún así puede anular la implementación predeterminada si es necesario.

0

Depende de cómo quiera implementarlo. Prefiero la inyección de constructores siempre que sienta que los valores que entran en la implementación no cambian con frecuencia. Por ejemplo: si la estrategia de compnay va con el servidor de oráculo, configuraré mis valores de origen de dats para las conexiones de un bean Achiveing ​​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 varias marcas a través de la inyección del dispositivo. Acabo de tomar un ejemplo, pero hay mejores formas de implementar los escenarios que mencioné anteriormente.

+1

Incluso cuando estoy programando en la empresa, siempre codigo desde el punto de vista que soy un contratista independiente que desarrolla código destinado a ser redistribuible. Supongo que va a ser de código abierto. De esta forma, me aseguro de que el código sea modular y conectable, y siga los principios SÓLIDOS. – Fred

0

La inyección de constructor revela explícitamente las dependencias, haciendo que el código sea más legible y menos propenso a errores de tiempo de ejecución no controlados si los argumentos se comprueban en el constructor, pero realmente se reduce a una opinión personal, y cuanto más se usa DI más, tenderá a inclinarse hacia adelante y hacia atrás de una forma u otra según el proyecto. Personalmente, tengo problemas con el código que huele a constructores con una larga lista de argumentos, y creo que el consumidor de un objeto debe conocer las dependencias para utilizar el objeto de todos modos, por lo que se justifica el uso de la inyección de propiedades. No me gusta la naturaleza implícita de la inyección de propiedades, pero me parece más elegante, lo que da como resultado un código de aspecto más limpio. Pero, por otro lado, la inyección de constructor ofrece un mayor grado de encapsulación, y en mi experiencia trato de evitar los constructores por defecto, ya que pueden tener un efecto negativo sobre la integridad de los datos encapsulados si no se tiene cuidado.

Elija la inyección por constructor o por propiedad sabiamente en función de su situación específica.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 el esfuerzo utilizar un patrón si el esfuerzo y la complejidad superan el beneficio. Mantenlo simple.

0

Esta es una entrada antigua, pero si es necesario en el futuro tal vez esto es de alguna utilidad:

https://github.com/omegamit6zeichen/prinject

que tenían una idea similar y se acercó con este marco. Probablemente esté lejos de ser completo, pero es una idea de un marco centrado en la inyección de propiedades

Cuestiones relacionadas