Pregunta

Bastante nuevo en la inyección de dependencia y estoy tratando de descubrir si se trata de un antipatrón.

Digamos que tengo 3 ensamblajes:

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

Foo.Users necesita un objeto creado dentro de Foo.Payment, y Foo.Payment también necesita cosas de Foo.Users.Esto crea una especie de dependencia circular.

He definido una interfaz en Foo.Shared que representa el marco de inyección de dependencia que estoy usando (en este caso NInject).

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

En la aplicación contenedora, tengo 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 se ve así:

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.Users 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 UserAccounts class están en este caso, lo que me hace pensar que no es una buena práctica.

¿De qué otra manera puedo lograr esto?

¿Alguna idea?

¿Fue útil?

Solución

Aunque algo controvertido:sí, este es un antipatrón.Se le conoce como un Localizador de servicios y aunque algunos lo consideran un patrón de diseño adecuado, yo lo considero un antipatrón.

Este problema es que el uso de, p.su clase UserAccounts se convierte implícito en lugar de explícito.Si bien el constructor indica que necesita un IDependencyResolver, no indica qué debe contener.Si le pasa un IDependencyResolver que no puede resolver ISomeType, se lanzará.

Lo peor es que en iteraciones posteriores, es posible que tengas la tentación de resolver algunos otro tipo desde dentro de Cuentas de usuario.Se compilará bien, pero es probable que se produzca en tiempo de ejecución si el tipo no se puede resolver.

No sigas ese camino.

A partir de la información proporcionada, es imposible decirte exactamente cómo deberías resolver tu problema particular con las dependencias circulares, pero te sugiero que reconsideres tu diseño.En muchos casos, las referencias circulares son un síntoma de Abstracciones con fugas, por lo que tal vez si remodelas un poco tu API, desaparecerá; a menudo es sorprendente los pequeños cambios que se requieren.

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

  • En muchos casos, un Publicar/suscríbete El modelo funciona bien.
  • El Mediador El patrón puede proporcionar una alternativa si la comunicación debe ir en ambos sentidos.
  • También puedes introducir un Fábrica abstracta para recuperar la instancia que necesita cuando la necesita, en lugar de requerir que se conecte de inmediato.

Otros consejos

Estoy de acuerdo con ForeverDebugging - que sería bueno para 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: Las clases que se ocupan solamente de los usuarios, no con el pago
  • Foo.UserPayment.dll: Las clases que tienen que ver tanto con el pago y los usuarios

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

Si usted tiene una dependencia circular entre los conjuntos, no necesariamente significa que tenga una dependencia circular entre las clases. Por ejemplo, suponga que tiene estas dependencias:

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

Se supone que no hay otras dependencias.

Aquí hay un círculo de conjuntos que van a depender el uno del otro en tiempo de ejecución en su aplicación (aunque no dependen unos de otros en tiempo de compilación, ya que pasan por la DLL compartida). Pero no hay círculo de clases que dependen unos de otros, en tiempo de compilación o en tiempo de ejecución.

En este caso, aún debe ser capaz de utilizar su contenedor IoC normalmente, sin añadir un nivel adicional de indirección. En su MyModule, simplemente obligar a cada interfaz del tipo de cemento correspondiente. Hacer cada clase aceptar sus dependencias como argumentos para el constructor. Cuando el código de aplicación de nivel superior necesita una instancia de una clase, dejar que se pida al contenedor IoC para la clase. Dejar que el COI contenedor preocupación sobre la búsqueda de todo lo que depende de 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 consumo Ninject, pero apoya la inyección propiedad - es aquí la documentación.

contenedores Normalmente IoC utilizan inyección de constructor - que pasan dependencias en 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 uno del otro, que había necesidad de pasar una instancia de la clase A en al constructor de la clase B. Sin embargo, con el fin de crear una A, que necesita para pasar una instancia de la clase B en su constructor. Es un problema de la gallina y el huevo.

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

Esto no es bastante sin embargo, y sin duda lo recomendaría Refactorizando las clases para eliminar dependencias circulares.

Eso parece un poco extraño para mí. ¿Es posible separar la lógica que necesita las dos referencias a cabo en un tercer conjunto de romper las dependencias y evitar el riesgo?

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top