如何使用依赖注入处理策略

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 的默认行为。