我必须创建一个具有本地和外部存储的订单系统。应始终先用完本地存储,如果仍然缺少任何内容,其余的应由外部存储订购。
基本上我有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。但是,对我来说,尚不清楚如何轻松地选择正确的。我的最佳做法是什么?
另外,如果可以更好地选择正确的策略,我也欢迎提出建议。谢谢你的时间!
最佳答案
您的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实例的依赖关系,或者它们是否可能更改。如果不是,并且/或者列表很短,那么您可能会坚持使用
new
;如果列表很长或可能会更改,则注入提供程序很有意义。如果您的实例在测试中难以使用,则注入Provider也很有意义,因为您可以将Providers.of()
与模拟一起使用,以简单的确定性测试双精度来代替真正的LocalReservator(其余的以此类推)。如果您希望以此为基础,还可以使用Guice multibindings(可让您以分布式方式在Guice中创建Set或Map,一次绑定一个)或Assisted Injection(可让您在使用您定义的工厂而不是Guice的提供者来创建您的Reservator。两者都不是立即适用的,但是两者都值得学习。
从这里开始,如果
setAmount
逻辑足够复杂以至于无法提取,则还可以将其提取到一种方法类ReservatorFactory中,该类选择要返回的Reservator。这样,您的ReservationHandler可以自己承担责任,无需进行特定的保留逻辑即可进行测试。 (当然,如果那是您希望ReservationHandler唯一要做的事情,那么请务必将该实现保留在原处。否则,您可以将ReservationHandler重构为一个空shell!)ps。杰克在评论中是正确的,将其标记为
@Singleton
可能会非常危险,同时保留单个预订的状态。您可以删除@Singleton
批注,Guice每次都会创建一个新实例。这是Guice的默认行为。关于java - 如何使用依赖注入(inject)来处理策略,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/43678738/