2009-09-26 15 views
8

Considere este ejemplo sencillo.Guice con dependencias circulares

Class A { 
    B b; 
    A() { 
     this.b = new B(this); 
    } 
} 

En este ejemplo instancia Una instancia sabe de B, y B instancia sabe de ejemplo A.

Mi pregunta es: ¿cómo crear una instancia de ejemplo A con Guice, es decir, cómo hacer Guice cuidar de estas complejas dependencias de círculo?

+0

Usted puede simplemente añadir @Inject al constructor de A. Estoy adivinando su clase real es algo más complicado. ¿B es una interfaz? ¿Necesita ser inyectado con algo además de A? Por cierto, dejar que el campo "este" escape al constructor es generalmente una mala idea. – NamshubWriter

+0

No, B no es una interfaz sino una clase. Por supuesto, las dependencias de círculo no son buenas y puedo refactorizar estas dos clases, pero lo que realmente necesito es comprender la viabilidad de Guice. –

Respuesta

4

Para responder a su primera pregunta "cómo crear instancias un ejemplo con Guice ": simplemente puede añadir @Inject al constructor:

class A { 
    private final B b; 

    @Inject 
    A() { 
     this.b = new B(this); 
    } 
} 

Esto funciona porque la API para crear A no tiene una dependencia circular. Guice solo usará el constructor A en cualquier momento que necesite para crear o inyectar un objeto A.

Si su pregunta es cómo usar Guice para crear un objeto donde la API para crear el objeto tiene una dependencia circular, vea this blog post by Misko Hevery (como se menciona en la respuesta de Yury).

+2

Ni siquiera necesita @Inject para un constructor sin argumentos. – ColinD

+0

Ese es exactamente el mismo código que el de Yury. El @Inject no tiene efecto y el código no desdobla A de B, que es el punto total de guice. – nes1983

+0

Sí, este es el mismo código que el OP escribió (con un @Inject para aclarar que el constructor es usado por Guice). El punto es que no hay una dependencia circular en la API, por lo que Guice puede crear A. Si este diseño es un buen diseño es una pregunta diferente. Puede ver algunas de mis preocupaciones en mis comentarios al PO, y el PO acordó que las dependencias circulares son una mala idea. – NamshubWriter

4

La respuesta es que no debe usar un marco de inyección de dependencia mientras tenga dependencias circulares en su código.

Por lo tanto, usted tiene que usted refactorizar código de antemano. Por lo que yo sé, hay dos soluciones para clases estrechamente conectadas: fusionar dos clases en una o introducir una nueva clase y mover la lógica común en ella (para detalles, busque here)

+4

Creo que necesita volver a leer la parte en negrita :) –

+0

De acuerdo con Yury. Las dependencias circulares con DI causan dolor. –

8

Su ejemplo no es un problema en absoluto, ya que se está construyendo B directamente. Pero si quieres que A y B sean creados por Guice, uno o ambos deberían ser una interfaz. Que puede hacer:

public interface A { /* skipping methods */ } 
public interface B { /* skipping methods */ } 

public class AImpl implements A { 
    private final B b; 

    @Inject 
    public AImpl(B b) { 
     this.b = b; 
    } 
    // ... 
} 

public class BImpl implements B { 
    private final A a; 

    @Inject 
    public BImpl(A a) { 
     this.a = a; 
    } 
    // ... 
} 

Incluso si AImpl y BImpl están en el ámbito como simple, Guice puede manejar esta inyección (por medio de un proxy). Esto funciona en un caso simple como este en cualquier caso ... Imagino que podría haber dependencias circulares más complejas que no podría manejar. De todos modos, eliminar las dependencias circulares sería preferible, por supuesto.

+0

Esto realmente funciona SOLAMENTE para singletons. ¿Qué pasa con el caso general? – nes1983

+0

¿Alguna idea sobre lo que ocurre detrás de la escena de esta inyección? –

3

creo que la propuesta de NamshubWriter no es muy guicy. Creo que en Guice, un constructor debería hacer exactamente una cosa: asignar parámetros a los campos. Si hay algo más que deba hacer, póngalo en una fábrica o en un proveedor.

En este caso, querremos un proveedor para A. El proveedor podría llamar directamente a la nueva B(), pero luego directamente uniríamos A a B, que es lo que intentamos evitar en primer lugar. Así que indirectamente, la creación de B sobre una fábrica, que Guice puede proporcionarnos a través de assistedInject. Este código se ejecuta y compila bien, y completamente desacopla A y B.

En un escenario realista, que había necesidad de ocultar detrás de A y B interfaces para tomar ventaja de la separación.

import com.google.inject.AbstractModule; 
import com.google.inject.Guice; 
import com.google.inject.Inject; 
import com.google.inject.Provider; 
import com.google.inject.assistedinject.Assisted; 
import com.google.inject.assistedinject.FactoryProvider; 

public class Try { 
    public static void main(String[] args) { 
     System.out.println(
       Guice.createInjector(new MyModule()).getInstance(A.class) 
     ); 
    } 
} 

class MyModule extends AbstractModule { 
    public void configure() { 
     bind(A.class).toProvider(AProvider.class); 
     bind(IBFactory.class).toProvider(
       FactoryProvider.newFactory(IBFactory.class, B.class)); 
    } 
} 

class A { 
    B b; 

    public void setB(B b) { 
     this.b = b;  
    } 
} 

class B { 
    A a; 

    @Inject 
    B(@Assisted A a) { 
     this.a = a; 
    } 
} 

class AProvider implements Provider<A> { 

    private final IBFactory bFactory; 

    @Inject 
    AProvider(IBFactory bFactory) { 
     this.bFactory = bFactory; 
    } 

    public A get() { 
     A a = new A(); 
     a.setB(bFactory.create(a)); 
     return a; 
    } 
} 

interface IBFactory { 
    public B create(A a); 
} 

I made an extended version of the circular dependency injection in Guice where A and B are hidden behind interfaces.

Cuestiones relacionadas