如何注入可变数量的具有依赖关系的类似组件?

时间:2016-11-07 17:03:45

标签: java python dependency-injection guice

背景

我希望使用injector(或pinject)在Python中实现依赖注入,而guice本身是从Modules should be fast and side-effect free大量借用的。虽然使用Python/injector的答案是理想的,但我对使用Java/guice的解决方案/方法感到非常高兴。

意图

我将快速总结一下我想要实现的目标:我有一个组件,它依赖于所有实现相同接口的其他组件的列表/序列。那些组件本身具有依赖性,这些依赖性可以在不同的实现中变化具体类型(实现)应由用户配置(或使用DI框架的任何机制)。

实施例

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

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

在这个例子中,租赁代理商(依赖于其他人的清单的组件)出租各种车辆(界面)。其车队中的特定车辆(在这种情况下为两辆车和一辆自行车)应该是可配置的,但在运行期间是固定的。车辆本身可以具有依赖性,并且它们可以根据车辆的类型而不同(汽车取决于电动机,在这种情况下自行车没有依赖性)。

问题

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

也许有帮助

Multibinder

我读过关于The docsinjector似乎与Binder.multibind类似的内容),它允许注入实现相同界面的对象集合。但是:

  • 是否可以用于创建需要接收不同依赖关系的同一个类的多个实例(示例中的两个汽车(Class Car)具有不同的电机:Interface MotorClass DieselEngineclass PetrolEngine)?
  • 使用提供程序完成该任务在我看来就像放弃依赖注入的好处:我可以在提供程序中手动创建Car实例,并将所需的Motor作为参数传递,但是因为这模式在链中进一步重复(即使用相同类型的多个Motor并且它们也具有依赖关系)我想使用依赖注入来生成这些对象。但是要在提供程序中手动使用它们,我似乎必须直接从进样器获取实例。 {{3}}提到注入注入器是一种罕见的情况,根据我对依赖注入的理解,最大的好处是可以请求组件,所有依赖项都由框架自动解决。
    另外因为我实际使用Python,我不确定这种方法是否合适(因为Python在动态代码生成方面非常灵活)。另外injector.Injector.get.__doc__提及
  

虽然此方法属于:class:Injector的公共接口   它意味着在有限的情况下使用。   例如,创建某种根对象(应用程序对象)   您的应用程序(请注意,只需要一次get次呼叫,   在Application类及其任何依赖项中   :func:inject可以而且应该使用):

1 个答案:

答案 0 :(得分:1)

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

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

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

对于帖子中的简单示例,请考虑创建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());
    }
}

正如你所提到的,这种情况有可能成为“工厂一路走下去”,但Guice可以帮助你。

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

如果您最终得到一组常用的依赖项和非常见的依赖项,您希望用它们来创建对象的不同“风格”,那么您就拥有了所谓的robot legs problem,然后Guice可以使用私有来解决它模块。

请注意Dagger 2 user guide

中的以下警告
  

注意:注入提供程序可能会造成混淆   代码,可能是错误的结构或错误结构的设计气味   图表中的对象。通常你会想要使用工厂或工厂   懒惰或重新组织代码的生命周期和结构   只能注入一个T.

如果您遵循此建议,您似乎必须小心平衡使用提供者和使用工厂来创建Vehicle