我必须创建一个具有本地和外部存储的订单系统。本地存储应始终先用完,如果还有缺少的内容,则应由外部存储订购剩余的内容。
基本上我有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);
}
}
现在我想知道如何通过依赖注入(inject)来处理三种预留策略(LocalReservator、ExternalReservator 和 MixedReservator),而不是实例化以允许更轻松的测试。
我知道我可以绑定(bind) Reservator-Interace 的所有实现并在注入(inject)时获取一个列表。然而,我并不完全清楚如何轻松选择正确的一个。针对我的情况,最佳实践是什么?
此外,如果可以更好地选择正确的策略,我愿意接受建议。感谢您的宝贵时间!
最佳答案
您的 ReservationHandler 当前至少负责做三件事:创建 Reservator 实例、计算要使用的 Reservator 以及充当 ReservationContext。虽然这段代码并不算太糟糕,但我们可以轻松地让 Guice 处理创建 Reservator 实例(毕竟,这是它的工作,封装依赖项创建),并考虑提取正确的 Reservator 的创建。如果 Reservator 的数量增加,或者 ReservationContext 的范围变得更清晰,这也将使您隔离。 (现在它看起来只是一个包装接口(interface)。)
您正在寻找的是一组提供程序,而不是列表:
private final Provider<LocalReservator> localReservatorProvider;
private final Provider<ExternalReservator> externalReservatorProvider;
private final Provider<MixedReservator> mixedReservatorProvider;
您可以通过在构造函数中从 Guice 接收它们来设置它们(Guice 可以为您在图表中绑定(bind)的任何 T 自动注入(inject) 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
;如果列表很长或者可能会发生变化,那么注入(inject)提供者就很有意义。如果您的实例在测试中难以使用,那么注入(inject)提供程序也是有意义的,因为您可以使用 Providers.of()
使用模拟来用简单的确定性测试替身替换真正的 LocalReservator(其余的依此类推)。
如果您想在此基础上进行构建,您还可以涉及 Guice multibindings (它允许您以分布式方式在 Guice 中创建一个 Set 或 Map,一次一个绑定(bind))或 Assisted Injection (这将允许您在创建 Reservator 期间传递构造函数参数,使用您定义的工厂而不是 Guice 的 Provider)。两者都不是立即适用的,但两者都值得学习。
从这里开始,如果您的setAmount
逻辑足够复杂,无法提取,您也可以将其提取到单方法类 ReservatorFactory 中,该类 ReservatorFactory 选择要返回的 Reservator。这样,您的 ReservationHandler 就可以由其自己负责,并且可以在不执行特定预订逻辑的情况下进行测试。 (当然,如果这是您希望 ReservationHandler 做的唯一事情,那么请务必将该实现保留在原处。否则您可以将 ReservationHandler 重构为空 shell!)
附: jack 在评论中是正确的,将其标记为 @Singleton
可能非常危险。同时它保留个人预订的状态。您可以删除 @Singleton
注解和Guice每次都会创建一个新实例;这是 Guice 的默认行为。
关于java - 如何处理依赖注入(inject)策略,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43678738/