Ghi đè Binding trong Guice


138

Tôi mới bắt đầu chơi với Guice và một trường hợp sử dụng mà tôi có thể nghĩ là trong một thử nghiệm tôi chỉ muốn ghi đè lên một ràng buộc duy nhất. Tôi nghĩ rằng tôi muốn sử dụng phần còn lại của các ràng buộc mức sản xuất để đảm bảo mọi thứ được thiết lập chính xác và để tránh trùng lặp.

Hãy tưởng tượng tôi có Module sau

public class ProductionModule implements Module {
    public void configure(Binder binder) {
        binder.bind(InterfaceA.class).to(ConcreteA.class);
        binder.bind(InterfaceB.class).to(ConcreteB.class);
        binder.bind(InterfaceC.class).to(ConcreteC.class);
    }
}

Và trong thử nghiệm của mình, tôi chỉ muốn ghi đè lên InterfaceC, trong khi vẫn giữ giao diện InterfaceA và InterfaceB, vì vậy tôi muốn một cái gì đó như:

Module testModule = new Module() {
    public void configure(Binder binder) {
        binder.bind(InterfaceC.class).to(MockC.class);
    }
};
Guice.createInjector(new ProductionModule(), testModule);

Tôi cũng đã thử những điều sau đây, không có may mắn:

Module testModule = new ProductionModule() {
    public void configure(Binder binder) {
        super.configure(binder);
        binder.bind(InterfaceC.class).to(MockC.class);
    }
};
Guice.createInjector(testModule);

Có ai biết nếu nó có thể làm những gì tôi muốn hoặc tôi hoàn toàn sủa sai cây ??

--- Theo dõi: Dường như tôi có thể đạt được những gì tôi muốn nếu tôi sử dụng thẻ @Im HiệnedBy trên giao diện và sau đó chỉ cung cấp một ràng buộc trong trường hợp thử nghiệm, hoạt động độc đáo khi có ánh xạ 1-1 giữa giao diện và cách thực hiện.

Ngoài ra, sau khi thảo luận điều này với một đồng nghiệp, có vẻ như chúng ta sẽ đi vào con đường ghi đè toàn bộ mô-đun và đảm bảo chúng ta có các mô-đun được xác định chính xác. Điều này có vẻ như có thể gây ra sự cố mặc dù liên kết bị đặt sai vị trí trong mô-đun và cần phải di chuyển, do đó có thể phá vỡ một loạt các bài kiểm tra vì các ràng buộc có thể không còn có thể bị quá tải.


7
Giống như cụm từ "sủa sai cây": D
Boris Pavlović

Câu trả lời:


149

Đây có thể không phải là câu trả lời mà bạn đang tìm kiếm, nhưng nếu bạn đang viết bài kiểm tra đơn vị, có lẽ bạn không nên sử dụng dụng cụ tiêm và thay vào đó là tiêm thuốc giả hoặc đồ vật giả bằng tay.

Mặt khác, nếu bạn thực sự muốn thay thế một ràng buộc duy nhất, bạn có thể sử dụng Modules.override(..):

public class ProductionModule implements Module {
    public void configure(Binder binder) {
        binder.bind(InterfaceA.class).to(ConcreteA.class);
        binder.bind(InterfaceB.class).to(ConcreteB.class);
        binder.bind(InterfaceC.class).to(ConcreteC.class);
    }
}
public class TestModule implements Module {
    public void configure(Binder binder) {
        binder.bind(InterfaceC.class).to(MockC.class);
    }
}
Guice.createInjector(Modules.override(new ProductionModule()).with(new TestModule()));

Xem chi tiết tại đây .

Nhưng như javadoc đã Modules.overrides(..)khuyến nghị, bạn nên thiết kế các mô-đun của mình theo cách mà bạn không cần phải ghi đè các ràng buộc. Trong ví dụ bạn đã đưa ra, bạn có thể thực hiện điều đó bằng cách di chuyển ràng buộc InterfaceCsang một mô-đun riêng.


9
Cảm ơn Albert, điều đó khiến tôi đôi khi xuống đường để làm những gì tôi muốn. Đó là trong một bản phát hành sản xuất chưa tho! Và đây là cho các thử nghiệm tích hợp, không phải thử nghiệm đơn vị, mà tôi tại sao tôi muốn đảm bảo mọi thứ khác đang được xây dựng chính xác
tddmonkey

1
Tôi đã thêm một ví dụ cụ thể vào mã. Nó có giúp bạn tiến xa hơn không?
albertb

1
Trừ khi tôi nhầm, ovveridemất đi sự đúng đắn Stagetrong khi làm như vậy (tức là PHÁT TRIỂN được sử dụng một cách có hệ thống).
pdeschen

4
Vấn đề kích cỡ. Khi biểu đồ phụ thuộc của bạn phát triển, việc nối dây bằng tay có thể khá khó khăn. Ngoài ra khi thay đổi hệ thống dây, bạn cần cập nhật thủ công tất cả các vị trí nối dây thủ công của bạn. Ghi đè cho phép bạn xử lý đó tự động.
yoosiba

3
@pdeschen Đó là một lỗi trong Guice 3 mà tôi đã sửa cho Guice 4.
Tavian Barnes

9

Tại sao không sử dụng thừa kế? Bạn có thể ghi đè các ràng buộc cụ thể của bạn trong overrideMephương thức, để lại các triển khai được chia sẻ trong configurephương thức.

public class DevModule implements Module {
    public void configure(Binder binder) {
        binder.bind(InterfaceA.class).to(TestDevImplA.class);
        overrideMe(binder);
    }

    protected void overrideMe(Binder binder){
        binder.bind(InterfaceC.class).to(ConcreteC.class);
    }
};

public class TestModule extends DevModule {
    @Override
    public void overrideMe(Binder binder) {
        binder.bind(InterfaceC.class).to(MockC.class);
    }
}

Và cuối cùng tạo ra trình tiêm của bạn theo cách này:

Guice.createInjector(new TestModule());

3
Các @Overridedường như không làm việc. Đặc biệt là nếu nó được thực hiện trên một phương pháp mà @Providesmột cái gì đó.
Sasanka Panguluri

4

Nếu bạn không muốn thay đổi mô-đun sản xuất của mình và nếu bạn có cấu trúc dự án giống như maven mặc định như

src/test/java/...
src/main/java/...

Bạn chỉ có thể tạo một lớp mới ConcreteCtrong thư mục thử nghiệm của mình bằng cách sử dụng gói tương tự như đối với lớp ban đầu của bạn. Sau đó Guice sẽ ràng buộc InterfaceCđến ConcreteCtừ thư mục thử nghiệm của bạn trong khi tất cả các giao diện khác sẽ bị ràng buộc đến các lớp học sản xuất của bạn.


2

Bạn muốn sử dụng Juckito nơi bạn có thể khai báo cấu hình tùy chỉnh cho từng lớp kiểm tra.

@RunWith(JukitoRunner.class)
class LogicTest {
    public static class Module extends JukitoModule {

        @Override
        protected void configureTest() {
            bind(InterfaceC.class).to(MockC.class);
        }
    }

    @Inject
    private InterfaceC logic;

    @Test
    public testLogicUsingMock() {
        logic.foo();
    }
}

1

Trong một thiết lập khác, chúng tôi có nhiều hơn một hoạt động được xác định trong các mô-đun riêng biệt. Hoạt động được đưa vào là trong Mô-đun Thư viện Android, với định nghĩa mô-đun RoboGuice của riêng nó trong tệp AndroidManifest.xml.

Các thiết lập trông như thế này. Trong Mô-đun Thư viện có các định nghĩa sau:

AndroidManifest.xml:

<application android:allowBackup="true">
    <activity android:name="com.example.SomeActivity/>
    <meta-data
        android:name="roboguice.modules"
        android:value="com.example.MainModule" />
</application>

Sau đó, chúng tôi có một loại được tiêm:

interface Foo { }

Một số triển khai mặc định của Foo:

class FooThing implements Foo { }

MainModule định cấu hình triển khai FooThing cho Foo:

public class MainModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(Foo.class).to(FooThing.class);
    }
}

Và cuối cùng, một Hoạt động tiêu thụ Foo:

public class SomeActivity extends RoboActivity {
    @Inject
    private Foo foo;
}

Trong Mô-đun ứng dụng Android tiêu thụ, chúng tôi muốn sử dụng SomeActivitynhưng, với mục đích thử nghiệm, hãy tự tiêm Foo.

public class SomeOtherActivity extends Activity {
    @Override
    protected void onResume() {
        super.onResume();

        Intent intent = new Intent(this, SomeActivity.class);
        startActivity(intent);
    }
}

Người ta có thể lập luận để phơi bày việc xử lý mô-đun cho ứng dụng khách, tuy nhiên, chúng ta cần che giấu hầu hết các thành phần đang được tiêm vì Mô-đun Thư viện là một SDK và việc phơi bày các phần có ý nghĩa lớn hơn.

(Hãy nhớ rằng, đây là để thử nghiệm, vì vậy chúng tôi biết nội bộ của someActivity và biết rằng nó tiêu thụ một Foo (gói có thể nhìn thấy).

Cách tôi thấy rằng công việc có ý nghĩa; sử dụng ghi đè được đề xuất để kiểm tra :

public class SomeOtherActivity extends Activity {
    private class OverrideModule
            extends AbstractModule {

        @Override
        protected void configure() {
            bind(Foo.class).to(OtherFooThing.class);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RoboGuice.overrideApplicationInjector(
                getApplication(),
                RoboGuice.newDefaultRoboModule(getApplication()),
                Modules
                        .override(new MainModule())
                        .with(new OverrideModule()));
    }

    @Override
    protected void onResume() {
        super.onResume();

        Intent intent = new Intent(this, SomeActivity.class);
        startActivity(intent);
    }
}

Bây giờ, khi SomeActivityđược bắt đầu, nó sẽ lấy ví dụ OtherFooThingđược tiêm Foo.

Đó là một tình huống rất cụ thể, trong trường hợp của chúng tôi, OtherFooThing đã được sử dụng nội bộ để ghi lại các tình huống kiểm tra, trong khi FooThing được sử dụng theo mặc định cho tất cả các mục đích sử dụng khác.

Hãy nhớ rằng, chúng tôi đang sử dụng #newDefaultRoboModuletrong các bài kiểm tra đơn vị của mình và nó hoạt động hoàn hảo.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.