如何注入数量不定的相似组件,这些组件本身具有依赖性?

How to inject a variable number of similar components that have dependencies themselves?

背景

我想在Python中使用injector (or pinject) which itself heavily borrows from guice实现依赖注入。虽然使用 Python/injector 的答案是理想的,但我也很高兴 solutions/approaches 这个功能 Java/guice.

意向

我将快速总结一下我想要实现的目标:我有一个组件依赖于 list/sequence 其他所有实现相同接口的组件。这些组件本身具有依赖性,这些依赖性可能因不同的实现而异。具体类型(实现)应由用户配置(或使用 DI 框架的任何机制)。

例子

是的,我读过 Modules should be fast and side-effect free 建议不要使用 XML 文件进行配置,但是由于我不知道如何在框架内实现这一点,我将使用一个演示依赖结构:

<RentingAgency>
    <Vehicles>
        <Car>
            <DieselEngine></DieselEngine>
        </Car>
        <Car>
            <PetrolEngine></PetrolEngine>
        </Car>
        <Bike></Bike>
    </Vehicles>
</RentingAgency>

在这个例子中有一个出租机构(依赖于其他列表的组件)出租各种车辆(接口)。他们车队中的特定车辆(在本例中是两辆汽车和一辆自行车)应该是可配置的,但在运行时是固定的。车辆本身可以具有依赖性,并且它们可以根据车辆类型而有所不同(汽车依赖于电机,在这种情况下自行车没有依赖性)。

问题

如何在 DI 框架内构建租赁机构,以便注入所有需要的车辆并正确解决它们的依赖关系?


可能有帮助

Multibinder

我读过 Multibinderinjector 似乎与 Binder.multibind 有相似之处)它允许注入实现相同接口的对象集合。然而:

Although this method is part of :class:Injector's public interface it's meant to be used in limited set of circumstances. For example, to create some kind of root object (application object) of your application (note that only one get call is needed, inside the Application class and any of its dependencies :func:inject can and should be used):

依赖注入框架主要用于依赖项,因为您的 Vehicles 对象是由用户在运行时配置的,它更像是应用程序数据而不是依赖项。它可能不能只使用 MultiBinding 一次注入,除非你在编译时知道它。

同样,你说的对,通过迭代和调用 injector.getInstance(Bike.class) 等来构建你的组件集不是一个好方法。首先,这不利于测试。

但是,由于 Vehicles 中包含的对象有自己的依赖项,您可以在创建 Vehicles 对象时利用 DI 框架。还要记住,虽然您不能将 Provider 绑定到实现,但当您绑定一个键时,Guice 将为您注入该提供程序。

对于post中的简单示例,考虑创建一个VehicleFactory。在里面,你可以有类似下面的东西:

public class VehicleModule implements Module {
    @Override
    public void configure(Binder binder) {
        binder.bind(DieselEngine.class).toProvider(DieselEngineProvider.class);
        binder.bind(PetrolEngine.class).toProvider(PetrolEngineProvider.class);
        binder.bind(Bike.class).toProvider(BikeProvider.class);
    }
}

public class DieselEngineProvider implements Provider<DieselEngine> {

    @Inject
    public DieselEngineProvider() {
        //if DieselEngine has any dependencies, they can be injected in the constructor
        //stored in a field in the class and used in the below get() method
    }

    @Override
    public DieselEngine get() {
        return new DieselEngine();
    }
}

public class VehicleFactory {

    private final CarFactory carFactory;
    private final Provider<Bike> bikeProvider;

    @Inject
    public VehicleFactory(CarFactory carFactory, Provider<Bike> bikeProvider) {
         this.carFactory = carFactory;
         this.bikeProvider = bikeProvider;
    }

    public Bike createBike() {
        return bikeProvider.get();
    }

    public Car createDieselCar() {
         return carFactory.createDieselCar();
    }

    public Car createPetrolCar() {
         return carFactory.createPetrolCar();
    }
}

public class CarFactory {
    private final Provider<DieselEngine> dieselEngineProvider;
    private final Provider<PetrolEngine> petrolEngineProvider;

    @Inject
    public CarFactory(Provider<DieselEngine> dieselEngineProvider, Provider<PetrolEngine> petrolEngineProvider) {
        this.dieselEngineProvider = dieselEngineProvider;
        this.petrolEngineProvider = petrolEngineProvider;
    }

    public Car createDieselCar() {
        return new Car(dieselEngineProvider.get());
    }

    public Car createPetrolCar() {
        return new Car(petrolEngineProvider.get());
    }
}

正如您所说,这有成为 'factories all the way down' 的危险,但 Guice 可以帮助您。

如果 Engine 的制作变得更加复杂并且涉及不同参数的组合,您可以使用 AssistedInject 等工具为您自动创建工厂。

如果你最终得到了一组你想用来创建不同 'flavours' 对象的公共依赖项和不常见依赖项,那么你就有了所谓的 robot legs problem 那么 Guice 可以使用私有模块解决它。

请注意 Dagger 2 user guide 中的以下警告:

Note: Injecting Provider has the possibility of creating confusing code, and may be a design smell of mis-scoped or mis-structured objects in your graph. Often you will want to use a factory or a Lazy or re-organize the lifetimes and structure of your code to be able to just inject a T.

如果您遵循此建议,您似乎必须仔细平衡使用提供程序和使用工厂来创建您的 Vehicle