如何使用依赖注入处理策略
How to handle a strategy with dependency injection
我必须创建一个具有本地和外部存储的订单系统。本地存储应该总是先用完,如果还缺少任何东西,剩下的就由外部存储来排序。
基本上我有4种可能的情况:
1 从本地存储订购所有物品
2 混合本地存储和外部存储的订单项目
3 从外部存储订购所有物品
4 无法订购
现在我决定为 3 个有效案例制作一个界面,界面非常简单:
public interface Reservator{
boolean reserveItem(Article article, amount);
}
现在我使用以下界面来选择策略:
public interface ReservationContext{
boolean setAmount(int amount);
boolean reserveItem(Article article);
}
其实现大致如下所示:
@Singleton
public ReservationHandler implements ReservationContext{
private int reservationAmount=0;
private Reservator reservator;
private LocalStorage local;
private ExternalStorage external;
@Inject
public ReservationHandler(LocalStorage local, ExternalStorage external){
this.local = local;
this.external = external;
}
@Override
public boolean setAmount(int amount){
if(amount == 0){
return false;
}
if(local.getStorage > amount){
reservator = new LocalReservator(//all dependencies);
}
else if(local.getStorage ==0 && external.getStorage > amount){
reservator = new ExternalReservator(//all dependencies);
}
else if((local.getStorage + external.getStorage) > amount){
reservator = new MixedReservator(//all dependencies);
}
else{
return false;
}
reservationAmount = amount;
return true;
}
@Override
public boolean reserveItem(Article article){
return reservator.reserveItem(article, amount);
}
}
现在我想知道如何通过依赖注入而不是实例化来处理三个预留策略(LocalReservator、ExternalReservator 和 MixedReservator)以允许更容易的测试。
我知道我可以绑定 Reservator-Interace 的所有实现并在注入时获得一个列表。然而,我并不完全清楚如何轻松地选择正确的。在我的案例中,最佳做法是什么?
此外,如果可以更好地处理正确策略的选择,我愿意接受建议。感谢您的宝贵时间!
您的 ReservationHandler 目前至少负责做三件事:创建 Reservator 实例、计算要使用的 Reservator 以及充当 ReservationContext。虽然这段代码并没有那么糟糕,但我们可以轻松地让 Guice 处理创建 Reservator 实例——毕竟这是它的工作,封装依赖项创建——并考虑提取正确的 Reservator 的创建。如果 Reservators 的数量增加,或者如果 ReservationContext 增长到具有更清晰的范围,这也将隔离您。 (现在它看起来只是一个包装界面。)
您要查找的不是列表,而是一组提供商:
private final Provider<LocalReservator> localReservatorProvider;
private final Provider<ExternalReservator> externalReservatorProvider;
private final Provider<MixedReservator> mixedReservatorProvider;
您可以通过在构造函数中从 Guice 接收它们来设置它们(它可以为您在图形中绑定的任何 T 自动注入 Provider<T>
),然后您可以在您的方法中调用它们.
if(local.getStorage > amount){
reservator = localReservatorProvider.get(); // no dependencies!
}
else if(local.getStorage ==0 && external.getStorage > amount){
reservator = externalReservatorProvider.get();
}
else if((local.getStorage + external.getStorage) > amount){
reservator = mixedReservatorProvider.get();
}
else{
return false;
}
你应该这样做吗? 现在你还没有向我们展示你的 Reservator 实例的依赖关系,或者它们是否可能会改变。如果不是,and/or 列表很短,您可能会坚持使用 new
;如果列表很长或可能会更改,则注入 Providers 很有意义。如果您的实例难以在测试中使用,那么注入 Providers 也是有意义的,因为您可以使用 Providers.of()
和模拟来用简单的确定性测试替身替换真正的 LocalReservator(其余部分依此类推) .
如果你想在此基础上构建,你还可以涉及 Guice multibindings (which lets you create a Set or Map in Guice in a distributed way, one binding at a time) or Assisted Injection(这将允许你在创建 Reservator 期间传递构造函数参数,使用你定义的 Factory 而不是 Guice 的 Provider)。两者都不是立即适用的,但都值得学习。
从这里开始,如果您的 setAmount
逻辑复杂到足以提取,您也可以将其提取到单一方法 class ReservatorFactory 中,该方法选择要 return 的 Reservator。这样,您的 ReservationHandler 就可以自行负责,并且可以在不执行您的特定预订逻辑的情况下进行测试。 (当然,如果这是您希望 ReservationHandler 做的唯一事情,那么一定要将该实现保留在原处。否则您可以将 ReservationHandler 重构为一个空 shell!)
p.s。杰克在评论中是正确的,将此标记为 @Singleton
是非常危险的,同时它会保持个人预订的状态。您可以删除 @Singleton
注释,Guice 每次都会创建一个新实例;这是 Guice 的默认行为。
我必须创建一个具有本地和外部存储的订单系统。本地存储应该总是先用完,如果还缺少任何东西,剩下的就由外部存储来排序。
基本上我有4种可能的情况:
1 从本地存储订购所有物品
2 混合本地存储和外部存储的订单项目
3 从外部存储订购所有物品
4 无法订购
现在我决定为 3 个有效案例制作一个界面,界面非常简单:
public interface Reservator{
boolean reserveItem(Article article, amount);
}
现在我使用以下界面来选择策略:
public interface ReservationContext{
boolean setAmount(int amount);
boolean reserveItem(Article article);
}
其实现大致如下所示:
@Singleton
public ReservationHandler implements ReservationContext{
private int reservationAmount=0;
private Reservator reservator;
private LocalStorage local;
private ExternalStorage external;
@Inject
public ReservationHandler(LocalStorage local, ExternalStorage external){
this.local = local;
this.external = external;
}
@Override
public boolean setAmount(int amount){
if(amount == 0){
return false;
}
if(local.getStorage > amount){
reservator = new LocalReservator(//all dependencies);
}
else if(local.getStorage ==0 && external.getStorage > amount){
reservator = new ExternalReservator(//all dependencies);
}
else if((local.getStorage + external.getStorage) > amount){
reservator = new MixedReservator(//all dependencies);
}
else{
return false;
}
reservationAmount = amount;
return true;
}
@Override
public boolean reserveItem(Article article){
return reservator.reserveItem(article, amount);
}
}
现在我想知道如何通过依赖注入而不是实例化来处理三个预留策略(LocalReservator、ExternalReservator 和 MixedReservator)以允许更容易的测试。
我知道我可以绑定 Reservator-Interace 的所有实现并在注入时获得一个列表。然而,我并不完全清楚如何轻松地选择正确的。在我的案例中,最佳做法是什么?
此外,如果可以更好地处理正确策略的选择,我愿意接受建议。感谢您的宝贵时间!
您的 ReservationHandler 目前至少负责做三件事:创建 Reservator 实例、计算要使用的 Reservator 以及充当 ReservationContext。虽然这段代码并没有那么糟糕,但我们可以轻松地让 Guice 处理创建 Reservator 实例——毕竟这是它的工作,封装依赖项创建——并考虑提取正确的 Reservator 的创建。如果 Reservators 的数量增加,或者如果 ReservationContext 增长到具有更清晰的范围,这也将隔离您。 (现在它看起来只是一个包装界面。)
您要查找的不是列表,而是一组提供商:
private final Provider<LocalReservator> localReservatorProvider;
private final Provider<ExternalReservator> externalReservatorProvider;
private final Provider<MixedReservator> mixedReservatorProvider;
您可以通过在构造函数中从 Guice 接收它们来设置它们(它可以为您在图形中绑定的任何 T 自动注入 Provider<T>
),然后您可以在您的方法中调用它们.
if(local.getStorage > amount){
reservator = localReservatorProvider.get(); // no dependencies!
}
else if(local.getStorage ==0 && external.getStorage > amount){
reservator = externalReservatorProvider.get();
}
else if((local.getStorage + external.getStorage) > amount){
reservator = mixedReservatorProvider.get();
}
else{
return false;
}
你应该这样做吗? 现在你还没有向我们展示你的 Reservator 实例的依赖关系,或者它们是否可能会改变。如果不是,and/or 列表很短,您可能会坚持使用 new
;如果列表很长或可能会更改,则注入 Providers 很有意义。如果您的实例难以在测试中使用,那么注入 Providers 也是有意义的,因为您可以使用 Providers.of()
和模拟来用简单的确定性测试替身替换真正的 LocalReservator(其余部分依此类推) .
如果你想在此基础上构建,你还可以涉及 Guice multibindings (which lets you create a Set or Map in Guice in a distributed way, one binding at a time) or Assisted Injection(这将允许你在创建 Reservator 期间传递构造函数参数,使用你定义的 Factory 而不是 Guice 的 Provider)。两者都不是立即适用的,但都值得学习。
从这里开始,如果您的 setAmount
逻辑复杂到足以提取,您也可以将其提取到单一方法 class ReservatorFactory 中,该方法选择要 return 的 Reservator。这样,您的 ReservationHandler 就可以自行负责,并且可以在不执行您的特定预订逻辑的情况下进行测试。 (当然,如果这是您希望 ReservationHandler 做的唯一事情,那么一定要将该实现保留在原处。否则您可以将 ReservationHandler 重构为一个空 shell!)
p.s。杰克在评论中是正确的,将此标记为 @Singleton
是非常危险的,同时它会保持个人预订的状态。您可以删除 @Singleton
注释,Guice 每次都会创建一个新实例;这是 Guice 的默认行为。