2009-09-26 36 views
8

Hãy xem ví dụ đơn giản này.Sử dụng Guice với phụ thuộc vòng tròn

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

Trong ví dụ trường hợp này A biết về dụ B, và dụ B biết về dụ A.

Câu hỏi của tôi là: làm thế nào để nhanh chóng dụ A với Guice, tức là làm thế nào để làm cho Guice chăm sóc của phụ thuộc vòng tròn phức tạp này?

+0

Bạn chỉ có thể thêm @Inject để các nhà xây dựng cho A. Tôi đoán lớp thực tế của bạn là chút phức tạp hơn. B là một giao diện? Liệu nó cần phải được tiêm với một cái gì đó bên cạnh A? BTW, cho phép trường "này" thoát khỏi hàm khởi tạo thường là một ý tưởng tồi. – NamshubWriter

+0

Không, B không phải là giao diện mà là một lớp. Tất nhiên, sự phụ thuộc vòng tròn không tốt và tôi có thể cấu trúc lại hai lớp này, nhưng điều tôi thực sự cần là hiểu tính khả thi của Guice. –

Trả lời

4

Để trả lời câu hỏi đầu tiên của bạn "cách khởi tạo Ví dụ A với Guice ": bạn chỉ có thể thêm @Inject để các nhà xây dựng:

class A { 
    private final B b; 

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

này hoạt động vì API để tạo A không có phụ thuộc vòng tròn. Guice sẽ chỉ sử dụng hàm tạo A bất cứ khi nào nó cần tạo hoặc tiêm đối tượng A.

Nếu câu hỏi của bạn là cách sử dụng Guice để tạo đối tượng nơi API tạo đối tượng có phụ thuộc vòng tròn, hãy xem this blog post by Misko Hevery (như đã đề cập trong câu trả lời của Yury).

+2

Bạn thậm chí không cần @Inject cho một nhà xây dựng no-args. – ColinD

+0

Đó chính xác là mã giống với Yury. Các @Inject không có hiệu lực và mã không decouble A từ B, đó là toàn bộ điểm của guice. – nes1983

+0

Có, đây là mã giống như OP đã viết (với @Inject để làm cho nó rõ ràng hơn rằng hàm tạo được sử dụng bởi Guice). Vấn đề là không có sự phụ thuộc vòng tròn trong API, vì vậy Guice có thể tạo ra A. Cho dù thiết kế này là một thiết kế tốt là một câu hỏi khác. Bạn có thể thấy một số mối quan tâm của tôi trong các ý kiến ​​của tôi cho OP, và OP đồng ý rằng phụ thuộc vòng tròn là một ý tưởng tồi. – NamshubWriter

4

Câu trả lời là bạn không nên sử dụng khung tiêm phụ thuộc trong khi bạn có phụ thuộc vòng tròn trong mã của mình.

Vì vậy, bạn phải refactor bạn mã trước. Theo như tôi biết, có hai giải pháp cho các lớp kết hợp chặt chẽ: hoặc kết hợp hai lớp thành một hoặc giới thiệu lớp mới và chuyển logic chung vào nó (để xem chi tiết here)

+4

Tôi nghĩ rằng bạn cần phải đọc lại phần đậm :) –

+0

Đồng ý với Yury. Thông tư phụ thuộc với DI gây đau. –

8

Ví dụ của bạn hoàn toàn không phải là vấn đề vì bạn đang xây dựng B trực tiếp. Nhưng nếu bạn muốn cả A và B được tạo ra bởi Guice, một hoặc cả hai nên là một giao diện. Bạn có thể làm:

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

Thậm chí nếu AImplBImpl được scoped như độc thân, Guice có thể xử lý tiêm này (bằng cách của một proxy). Điều này làm việc trong một trường hợp đơn giản như thế này ở mức nào ... Tôi tưởng tượng có thể có nhiều phụ thuộc vòng tròn phức tạp hơn mà nó không thể xử lý được. Dù sao, loại bỏ phụ thuộc vòng tròn sẽ là thích hợp hơn, tất nhiên.

+0

Điều này thực sự hoạt động CHỈ dành cho người độc thân. Điều gì về trường hợp chung? – nes1983

+0

Bất kỳ ý tưởng nào về những gì đang diễn ra sau cảnh tiêm này? –

3

Tôi nghĩ rằng đề xuất của NamshubWriter không phải là rất có ý nghĩa. Tôi nghĩ rằng trong Guice, một nhà xây dựng nên làm chính xác một điều: gán các tham số vào các trường. Nếu bạn cần làm gì khác, hãy đặt nó vào một nhà máy hoặc một nhà cung cấp.

Trong trường hợp này, chúng tôi sẽ muốn một nhà cung cấp cho A. Nhà cung cấp có thể gọi trực tiếp B mới(), nhưng sau đó chúng tôi trực tiếp ghép đôi A đến B, đó là những gì chúng tôi đã cố gắng tránh ngay từ đầu. Vì vậy, chúng tôi gián tiếp tạo ra B trên một nhà máy, mà guice có thể cung cấp cho chúng tôi thông qua assistedInject. Mã này chạy và biên dịch tốt và hoàn toàn tách A và B.

Trong trường hợp thực tế, bạn cần ẩn A và B phía sau giao diện để tận dụng lợi thế của việc tách.

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.

Các vấn đề liên quan