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

时间:2017-04-28 11:07:45

标签: java dependency-injection guice strategy-pattern

我必须创建一个具有本地和外部存储的订单系统。应始终首先使用本地存储,如果仍有任何缺失,则应由外部存储器订购。

基本上我有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实现并在注入时获取List。然而,我并不完全清楚如何轻松地选择正确的方法。在我的案例中,最佳做法是什么?

如果能够更好地处理正确策略的选择,我可以接受建议。谢谢你的时间!

1 个答案:

答案 0 :(得分:2)

您的ReservationHandler目前负责至少做三件事:创建Reservator实例,计算要使用的Reservator,以及充当ReservationContext。虽然这段代码并不是太糟糕,但是我们可以很容易地让Guice处理创建Reservator实例 - 毕竟它的工作是封装依赖项创建 - 并考虑提取创建正确的储存器。如果Reserveators的数量增加,或者如果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实例的依赖关系,或者他们是否可能会改变。如果他们不是,和/或列表很短,您可能会坚持使用new;如果列表很长或可能会改变,那么注入提供者会很有意义。如果您的实例难以在测试中使用,那么注入Providers也是有意义的,因为您可以使用Providers.of()和mock来用简单的确定性测试double替换真正的LocalReservator(等等)

如果你想在此基础上构建,你还可以参与Guice multibindings(它允许你以分布式方式在Guice中创建一个Set或Map,一次一个绑定)或Assisted Injection(可以让你在创建预备库时传递构造函数参数,使用你定义的工厂而不是Guice的提供者。两者都不是立即适用的,但两者都值得学习。

从这里开始,如果你的setAmount逻辑足够复杂,你也可以将它提取到一个方法类ReservatorFactory,它选择要返回的Reservator。这样,您的ReservationHandler可以自行承担责任,并且可以在不执行特定预留逻辑的情况下进行测试。 (当然,如果您希望ReservationHandler做的唯一事情,那么请务必将该实现留在原处。否则您可以将ReservationHandler重构为空shell!)

P.S。杰克在评论中是正确的,将@Singleton标记为@Singleton同时保留个人预订状态可能非常危险。您可以删除api.get('some server url').then(function(data) { if (data.SomeDate) data.SomeDate = new Date(data.SomeDate); });注释,Guice每次都会创建一个新实例;这是Guice的默认行为。