2009-09-26 11 views
8

Rozważ ten prosty przykład.Używanie Guice z kołowymi zależnościami

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

w tym przykładzie przykład wie o instancji B i B wie o wystąpienie instancji A.

Moje pytanie brzmi: jak instancję przykład z Guice, czyli jak zrobić Guice dbać tej złożonej zależności koło?

+0

można po prostu dodać @Inject do konstruktora A. Zgaduję aktualną klasę jest nieco bardziej skomplikowana. Czy B jest interfejsem? Czy trzeba wstrzykiwać coś poza A? Przyzwalanie, że "to" pole ucieka przed konstruktorem, to generalnie zły pomysł. – NamshubWriter

+0

Nie, B nie jest interfejsem, ale klasą. Oczywiście, zależności między okręgami nie są dobre i mogę refaktoryzować te dwie klasy, ale to, czego naprawdę potrzebuję, to zrozumieć wykonalność Guice. –

Odpowiedz

4

Aby odpowiedzieć na pierwsze pytanie "jak utworzyć instancję przykład z Guice ": można po prostu dodać @Inject do konstruktora:

class A { 
    private final B b; 

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

To działa, ponieważ API do tworzenia A nie posiada okrągły zależność. Guice użyje po prostu konstruktora A za każdym razem, gdy potrzebuje utworzyć obiekt lub wstawić obiekt A.

Jeśli masz pytanie, jak użyć Guice do stworzenia obiektu, w którym interfejs API do tworzenia obiektu ma zależność cykliczną, zobacz this blog post by Misko Hevery (jak wspomniano w odpowiedzi Jurija).

+2

Nie potrzebujesz nawet @Inject dla konstruktora no-args. – ColinD

+0

To jest ten sam kod co Yury. @Inject nie przynosi żadnego efektu, a kod nie eliminuje A z B, który jest całym punktem guice. – nes1983

+0

Tak, jest to ten sam kod co OP napisano (z @Inject, aby uczynić jaśniejszym, że konstruktor jest używany przez Guice). Chodzi o to, że w interfejsie API nie istnieje zależność cykliczna, więc Guice może utworzyć A. Niezależnie od tego, czy ten projekt jest dobrym projektem, to inne pytanie. Niektóre moje obawy można dostrzec w moich komentarzach do PO, a PO zgodził się, że zależności cykliczne są złym pomysłem. – NamshubWriter

4

Odpowiedź brzmi: nie powinieneś używać szkieletu zależności wtrysku, podczas gdy w twoim kodzie masz zależność kołową.

Musisz więc wcześniej poprawić kod. O ile mi wiadomo, istnieją dwa rozwiązania dla ściśle powiązanych klas: albo łącz dwie klasy w jedną, albo wprowadź nową klasę i przenieś do niej wspólną logikę (dla wyglądu szczegółowego: here)

+4

Myślę, że trzeba ponownie przeczytać odważną część :) –

+0

Uzgodniono z jurorem. Okrężne zależności z DI powodują ból. –

8

Twój przykład wcale nie jest problemem, ponieważ konstruujesz B bezpośrednio. Ale jeśli chcesz stworzyć A i B przez Guice, jeden lub oba powinny być interfejsem. Można to zrobić:

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; 
    } 
    // ... 
} 

Nawet jeśli AImpl i BImpl są scoped jako pojedynczych, Guice może obsługiwać ten zastrzyk (za pomocą serwera proxy). Działa to w takim prostym przypadku, jak to w każdym razie ... Wyobrażam sobie, że mogą istnieć bardziej złożone zależności cykliczne, z którymi nie mógłby sobie poradzić. W każdym razie wyeliminowanie zależności cyklicznych byłoby oczywiście lepsze.

+0

To naprawdę działa TYLKO dla singletonów. A co z ogólnym przypadkiem? – nes1983

+0

Jakieś pomysły na temat tego, co dzieje się za sceną tego zastrzyku? –

3

Uważam, że propozycja NamshubWriter nie jest zbyt ostra. Myślę, że w Guice konstruktor powinien zrobić dokładnie jedną rzecz: przypisać parametry do pól. Jeśli jest coś jeszcze, co musisz zrobić, umieść w fabryce lub dostawcy.

W tym przypadku będziemy potrzebować operatora dla A. Dostawca może bezpośrednio wywołać nowe B(), ale wtedy będziemy bezpośrednio łączyć A do B, czego staraliśmy się uniknąć w pierwszej kolejności. Tak więc pośrednio tworzymy B w fabryce, która może nam zapewnić guess poprzez assistedInject. Ten kod działa i kompiluje się dobrze, a całkowicie oddziela A i B.

W realistycznym scenariuszu trzeba ukryć interfejsy A i B za sobą, aby skorzystać z separacji.

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.