2009-12-12 14 views
8

Bastante nuevo en la inyección de dependencia y estoy tratando de averiguar si este es un patrón anti.Inyectando el Inyector de Dependencia usando la Inyección de Dependencia

Digamos que tengo 3 asambleas:

Foo.Shared - this has all the interfaces 
Foo.Users - references Foo.Shared 
Foo.Payment - references Foo.Shared 

Foo.Users necesita un objeto que se construye dentro Foo.Payment y Foo.Payment también necesita cosas de Foo.Users. Esto crea algún tipo de dependencia circular.

He definido una interfaz en Foo.Shared que representa el marco de Inyección de Dependencia que estoy utilizando (en este caso NInject).

public interface IDependencyResolver 
{ 
    T Get<T>(); 
} 

En la aplicación contenedora, que tienen una implementación de esta interfaz:

public class DependencyResolver:IDependencyResolver 
{ 
    private readonly IKernel _kernel; 

    public DependencyResolver(IKernel kernel) 
    { 
     _kernel = kernel; 
    } 

    public T Get<T>() 
    { 
     return _kernel.Get<T>(); 
    } 
} 

La configuración es el siguiente:

public class MyModule:StandardModule 
{ 
    public override void Load() 
    { 
     Bind<IDependencyResolver>().To<DependencyResolver>().WithArgument("kernel", Kernel); 
     Bind<Foo.Shared.ISomeType>().To<Foo.Payment.SomeType>(); // <- binding to different assembly 
     ... 
    } 
} 

Esto me permite crear una instancia de un nuevo objeto de Foo.Payment.SomeType desde dentro de Foo.Usuarios sin necesidad de una referencia directa:

public class UserAccounts:IUserAccounts 
{ 
    private ISomeType _someType; 
    public UserAccounts(IDependencyResolver dependencyResolver) 
    { 
     _someType = dependencyResolver.Get<ISomeType>(); // <- this essentially creates a new instance of Foo.Payment.SomeType 
    } 
} 

Esto hace que no quede claro cuáles son las dependencias exactas de la clase UserAccounts en esta instancia, lo que me hace pensar que no es una buena práctica.

¿De qué otra forma puedo lograrlo?

¿Alguna idea?

+1

+1 solo para el título de trabalenguas. – womp

+0

mismo aquí, me encanta el título :) –

Respuesta

7

Aunque es un tanto controvertido: sí, este es un antipatrón. Se lo conoce como Service Locator y aunque algunos lo consideran un patrón de diseño adecuado, lo considero un antipatrón.

Este problema es el uso de, p. su clase UserAccounts pasa a ser implícita en lugar de explícita. Mientras que el constructor declara que necesita un IDependencyResolver, no indica qué debe ir en él. Si le pasa un IDependencyResolver que no puede resolver ISomeType, se lanzará.

Lo que es peor es que en iteraciones posteriores, puede tener la tentación de resolver algunos otro tipo dentro de UserAccounts. Va a compilar muy bien, pero es probable que arroje en tiempo de ejecución si/cuando el tipo no se puede resolver.

No siga esa ruta.

Según la información proporcionada, es imposible decirle exactamente cómo debe resolver su problema particular con las dependencias circulares, pero le sugiero que reconsidere su diseño. En muchos casos, las referencias circulares son un síntoma de Leaky Abstractions, por lo que quizás si remodelas un poco tu API, desaparecerá; a menudo resulta sorprendente cómo se requieren pequeños cambios.

En general, la solución a cualquier problema es agregar otra capa de indirección. Si realmente necesita que los objetos de ambas bibliotecas colaboren estrechamente, puede presentar un intermediario intermedio.

  • En muchos casos, un publicación/suscripción modelo funciona bien.
  • Mediator patrón puede proporcionar una alternativa si la comunicación debe ir en ambos sentidos.
  • También puede introducir Abstract Factory para recuperar la instancia que necesita cuando la necesita, en lugar de requerir que se la conecte de inmediato.
+0

Eso es lo que pensé que las dependencias no son claras, por lo que debe ser un antipatrón. :) Una fábrica abstracta fue mi primera opción, pero supercomplica el código. En la aplicación real, necesito crear muchos tipos separados, no solo uno. Yo codifico cada método de fábrica diferente, o uso genéricos para asociar una interfaz a una clase concreta (también codificada). Pero pierdo el poder del marco de inyección de dependencia de esta manera, y se volverá realmente complicado configurar las vinculaciones, incluso hasta el punto de necesitar el código de inyección de dependencia manual/resolver el tipo utilizando la reflexión. – andreialecu

+0

Siempre hay que pagar un precio :) No estoy de acuerdo con que se convierta en un desastre configurar el contenedor; puede llegar a ser bastante prolijo y prolijo, pero va a consistir en un montón de código casi declarativo, que es un excelente compensación. Gran parte de la agilidad que puede resolver con la configuración basada en la convención, especialmente si termina con muchas fábricas abstractas similares que deben configurarse del mismo modo. Castle Windsor tiene una funcionalidad que le permite configurar por convención en unas simples declaraciones: no sé si NInject puede hacer esto también ... –

1

Eso me parece un poco extraño. ¿Es posible separar la lógica que necesita ambas referencias en un tercer conjunto para romper las dependencias y evitar el riesgo?

2

Estoy de acuerdo con ForeverDebugging - sería bueno eliminar la dependencia circular. A ver si puedes separar las clases de la siguiente manera:

  • Foo.Payment.dll: Las clases que tienen que ver únicamente con el pago, no con los usuarios
  • Foo.Users.dll: Clases que tienen que ver únicamente con los usuarios, no con
  • pago
  • Foo.UserPayment.dll: Las clases que tienen que ver tanto con el pago y los usuarios

Entonces usted tiene un conjunto que hace referencia a los otros dos, pero ningún círculo de dependencias.

Si tiene una dependencia circular entre ensamblajes, no necesariamente significa que tiene una dependencia circular entre clases. Por ejemplo, suponga que tiene estas dependencias:

  • Foo.Users.UserAccounts depende de Foo.Shared.IPaymentHistory, que se realiza mediante Foo.Payment.PaymentHistory.
  • Una clase de pago diferente, Foo.Payment.PaymentGateway, depende de Foo.Shared.IUserAccounts. IUserAccounts es implementado por Foo.Users.UserAccounts.

Supongamos que no hay otras dependencias.

Aquí hay un círculo de ensamblados que dependerán entre sí en tiempo de ejecución en su aplicación (aunque no dependen el uno del otro en tiempo de compilación, ya que pasan por la DLL compartida). Pero no existe un círculo de clases que dependan entre sí, en tiempo de compilación o en tiempo de ejecución.

En este caso, aún así debería poder usar su contenedor IoC normalmente, sin agregar un nivel adicional de indirección. En su MyModule, simplemente vincule cada interfaz al tipo de concreto apropiado. Haga que cada clase acepte sus dependencias como argumentos para el constructor. Cuando su código de aplicación de nivel superior necesita una instancia de una clase, permita que pregunte al contenedor de IoC por la clase. Deje que el contenedor de IoC se preocupe por encontrar todo de lo que depende la clase.



Si lo hace terminar con una dependencia circular entre las clases, es probable que necesite usar la inyección de propiedad (también conocido como inyección de setter) en una de las clases, en lugar de la inyección de constructor. No uso Ninject, pero sí admite inyección de propiedad - here is the documentation.

Normalmente, los contenedores IoC usan la inyección de constructor: pasan dependencias al constructor de la clase que depende de ellos. Pero esto no funciona cuando hay una dependencia circular. Si las clases A y B dependen unas de otras, necesitarás pasar una instancia de clase A al constructor de clase B. Pero para crear una A, necesitas pasar una instancia de clase B a su constructor. Es un problema de gallina y huevo.

Con la inyección de propiedad, le dice a su contenedor IoC que primero llame al constructor y luego establezca una propiedad en el objeto construido. Normalmente esto se usa para dependencias opcionales, como los registradores. Pero también podría usarlo para romper una dependencia circular entre dos clases que se requieren mutuamente.

Esto no es muy bonito, y definitivamente recomendaría refactorizar sus clases para eliminar las dependencias circulares.

Cuestiones relacionadas