c# - utilizar - Inyección de dependencia: ¿qué hacer cuando tienes muchas dependencias?
patron inyeccion de dependencias ventajas (4)
¿Puedes justificar (a ti mismo) por qué la clase depende de otras 10 clases? ¿Hay variables miembro que use para unir un subconjunto de esas clases? Si es así, eso indica que esta clase debe dividirse para que la clase extraída dependa del subconjunto y las variables que vinculan dicho estado vayan en la clase extraída. Con 10 dependencias, es posible que esta clase simplemente haya crecido demasiado y que de todos modos tenga que ser dividida.
Una nota con respecto a su oración final: tal dependencia de orden también puede ser un olor a código, por lo que probablemente sea bueno no exponerlo en su interfaz. De hecho, considere si los requisitos de pedido son o no porque las operaciones deben llevarse a cabo en un orden específico (es la complejidad del algoritmo o protocolo) o porque ha diseñado sus clases para que sean interdependientes. Si la complejidad se debe a su diseño, vuelva a configurar para eliminar la dependencia ordenada siempre que sea posible.
Si no puede refactorizar (las complejidades son esenciales y solo tiene un terrible problema de coordinación en sus manos), puede abstraer la fealdad y mantener protegidos a los usuarios de esta clase (constructor, fábrica, inyector, etc.).
Editar: Ahora que lo he pensado, no estoy convencido de que las complejidades esenciales de su algoritmo o protocolo no se puedan abstraer un poco (aunque ese podría ser el caso). Dependiendo de su problema específico, las similitudes en las manipulaciones de esas clases dependientes podrían resolverse mejor con el patrón de Estrategia o el patrón Observer (detectores de eventos). Es posible que deba envolver estas clases en clases que las adapten a interfaces ligeramente diferentes de las que exponen actualmente. Tendría que evaluar la compensación de tener el código en esta clase de monstruos ser más legible (yay) a expensas de hasta 10 clases más en su proyecto (boo).
También me gustaría hacer un apéndice para resumir la construcción de esta clase. Parece importante que cualquier clase que dependa de esta clase también use el patrón de Inyección de Dependencia. De esta forma, si usa un generador, una fábrica, un inyector, etc., no se despoja accidentalmente de algunos de los beneficios del uso del patrón DI (lo más importante en mi mente es la capacidad de sustituir objetos simulados para probar) .
Editar 2 (basado en su edición):
Mi primer pensamiento es "¿qué, ninguna dependencia de registro?" :)
Incluso sabiendo cuáles son las dependencias, es difícil ofrecer consejos útiles.
Primero: ¿cuáles son las responsabilidades de todos? ¿Por qué esta clase depende del código del controlador (la lógica comercial) y del código del Modelo (dos clases diferentes de acceso a la base de datos, con clases DAO)?
Dependiendo tanto de los DAO como de las clases de acceso a DB hay un olor a código. ¿Cuál es el propósito de un DAO? ¿Cuál es el propósito de las clases DB? ¿Estás tratando de operar en múltiples niveles de abstracción?
Uno de los principios de OO es que los datos y el comportamiento se agrupan en pequeñas cosas llamadas clases. ¿Has violado esto cuando creaste esta clase de lógica de negocios distinta de los objetos que manipula distintos del DAO distinto de esta clase? Relacionado: tome una breve distracción en SOLID .
Segundo: una clase para cargar configuraciones. Huele mal. La Inyección de Dependencia lo ayuda a identificar las dependencias y a cambiarlas. Tu clase de monstruo que depende de ciertos parámetros. Estos parámetros se agrupan en esta clase de configuración porque ...? ¿Cuál es el nombre de esta clase de configuración? ¿Es DBparameters? si es así, pertenece al objeto (s) DB, no a esta clase. ¿Es genérico como las Configuraciones? Si es así, tienes un mini inyector de dependencia ahí mismo (dado que probablemente solo está inyectando valores de cadena o int en lugar de datos compuestos como clases, pero ¿por qué?). Torpe.
Tercero: la lección más importante que aprendí de la Refactorización fue que mi código era desagradable. No solo mi código era malo, sino que no hubo una transformación única para que dejara de chupar. Lo mejor que podía desear era hacer que chupara menos. Una vez que hice eso, pude hacer que mamara menos de nuevo. Y otra vez. Algunos patrones de diseño son malos, pero existen para permitir que su sucky code haga la transición a un código menos sucky. Entonces tomas tus globals y los conviertes en singletons. Luego eliminas tus singletons. No se desanime porque acaba de rediseñar para encontrar que su código todavía apesta. Apesta menos. Por lo tanto, su objeto de carga de Configuración puede oler, pero puede decidir que no es la parte más olorosa de su código. De hecho, es posible que el esfuerzo por "arreglarlo" no valga la pena.
Tengo una clase A que depende de otras 10 clases. De acuerdo con el patrón de Inyección de Dependencia, debería pasar todas las dependencias de A por su constructor.
Así que supongamos que este constructor (por supuesto, este no es un código de trabajo o real, ya que no puedo publicar el código real aquí)
public ClassA(ClassB b, ClassC c, ClassD d, ClassE e, ClassF f, ClassG g, ClassH h, ClassI i) {
this.b = b;
this.c = c;
this.d = d;
this.e = e;
this.f = f;
this.g = g;
this.h = h;
this.i = i;
}
He leído en el libro de Martin Fowler sobre la refacturación de que tener un método con muchos parámetros es un olor codificado y no debería suceder.
Mi pregunta es: ¿está bien cuando estamos hablando de DI? ¿Hay una forma mejor de inyectar dependencias sin romper las reglas de Martin Fowler?
Sé que podría pasar las dependencias a través de propiedades, pero eso puede causar errores, ya que nadie está realmente seguro de lo que debe pasar para que la clase funcione.
EDITAR
Gracias por todas sus respuestas. Trataré ahora de demostrar algunas de las dependencias de clase A:
1 - Una clase para acceder a una base de datos
2 - Otra clase para acceder a otro DB (sí, necesito realizar operaciones en dos bases de datos)
3 - Una clase para enviar notificaciones de error por correo electrónico
4 - Una clase para cargar configuraciones
5 - Una clase que actuará como temporizador para algunas operaciones (tal vez esta se puede evitar)
6 - Una clase con lógica de negocios
Hay muchos otros de los que estoy tratando de deshacerme, pero esos son realmente necesarios y no veo ninguna forma de evitarlos.
EDITAR
Después de algunas refactorizaciones ahora tengo 7 dependencias (por debajo de 10). Pero tengo 4 objetos DAO
:
ClienteDAO
ProcessDAO
ProductosDAO
CatalogDAO
¿Es correcto crear otra clase llamada MyProjectDAO e inyectar esos DAOS en ella? De esta forma tendré solo una clase DAO que agregue todos los objetos DAO de mi proyecto. No creo que sea una buena idea porque viola el Principio de Responsabilidad Individual. ¿Estoy en lo cierto?
En mi experiencia:
- Intenta diseñar tu clase para que necesite menos dependencias. Si necesita tantos, puede tener demasiadas responsabilidades.
- Si realmente está convencido de que su diseño de clase es apropiado, considere si puede tener sentido que algunas de esas dependencias se unan (por ejemplo, a través de un adaptador que asume la responsabilidad de una operación "grande" que su clase necesita delegando a unas pocas de las dependencias). Luego puede depender del adaptador en lugar de las dependencias "más pequeñas".
- Si cualquier otro bit realmente tiene sentido, simplemente trague el olor de tener muchos parámetros. Pasa algunas veces.
Sí, un método que tome tantos parámetros debería considerarse un olor codificado. ¿Este método realmente solo está haciendo una cosa y una sola cosa?
Si esto sigue siendo cierto, aún se puede reducir el número de dependencias al observar las relaciones entre las dependencias. ¿Alguno de ellos está estrechamente relacionado? ¿Se podrían acoplar en dependencias agregadas? Por ejemplo, podrías refactorizar creando una nueva clase K que use A, B y C internamente (inyectado en la clase K por constructor, luego usando composición), de modo que el número de parámetros para el método se reduciría en dos.
Enjuague y repita hasta que la agregación ya no tenga sentido y / o tenga una cantidad razonable de parámetros.
También vea una publicación de blog relacionada: "Refactorización para agregar servicios"
También te aconsejo rediseñar tu aplicación. En caso de que no sea posible, puede pasar su contenedor IoC como parámetro de constructor. Si no desea vincular su código con una implementación concreta, siempre puede abstraerlo. El código se verá algo como esto.
public interface IAbstractContainer
{
T Resolve<T>();
}
public class ConcreteContainer: IAbstractContainer
{
private IContainer _container; // E.g. Autofac container
public ConcreteContainer(IContainer container)
{
_container = container;
{
public T Resolve<T>()
{
return _container.Resolve<T>();
}
}
public classA(IAbstractContainer container)
{
this.B = container.Resolve<ClassB>();
this.C = container.Resolve<ClassC>();
...
}
}
Una instancia de ConcreteContainer
se inyecta de la manera habitual.