如何注入数量不定的相似组件,这些组件本身具有依赖性?
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
我读过 Multibinder(injector
似乎与 Binder.multibind
有相似之处)它允许注入实现相同接口的对象集合。然而:
- 是否可以用来创建同一个class的多个实例,需要接收不同的依赖(例子中的两辆车(
Class Car
)有不同的电机:Interface Motor
, Class DieselEngine
, class PetrolEngine
)?
- 使用提供者来完成这个任务在我看来就像放弃依赖注入的好处:我可以在提供者中手动创建
Car
实例,传递所需的 Motor
作为参数,但是因为这种模式在链的更下方重复(即使用多个相同类型的 Motor
并且它们也具有依赖关系)我也想使用依赖注入来生成这些对象。但是要在提供程序中手动使用它们,在我看来我必须直接从注入器获取实例。 The docs 提到注入注入器是一种罕见的情况,根据我对依赖注入的理解,最大的好处是可以请求一个组件并且所有依赖项都由框架自动解析。
另外因为我实际上使用 Python 我不确定这种方法是否合适(因为 Python 在动态代码生成方面非常灵活)。另外 injector.Injector.get.__doc__
提到
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
。
背景
我想在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
我读过 Multibinder(injector
似乎与 Binder.multibind
有相似之处)它允许注入实现相同接口的对象集合。然而:
- 是否可以用来创建同一个class的多个实例,需要接收不同的依赖(例子中的两辆车(
Class Car
)有不同的电机:Interface Motor
,Class DieselEngine
,class PetrolEngine
)? - 使用提供者来完成这个任务在我看来就像放弃依赖注入的好处:我可以在提供者中手动创建
Car
实例,传递所需的Motor
作为参数,但是因为这种模式在链的更下方重复(即使用多个相同类型的Motor
并且它们也具有依赖关系)我也想使用依赖注入来生成这些对象。但是要在提供程序中手动使用它们,在我看来我必须直接从注入器获取实例。 The docs 提到注入注入器是一种罕见的情况,根据我对依赖注入的理解,最大的好处是可以请求一个组件并且所有依赖项都由框架自动解析。
另外因为我实际上使用 Python 我不确定这种方法是否合适(因为 Python 在动态代码生成方面非常灵活)。另外injector.Injector.get.__doc__
提到
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 oneget
call is needed, inside theApplication
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
。