依赖注入:绑定到构造函数参数的特定值

时间:2014-04-15 21:55:22

标签: java android dependency-injection guice roboguice

背景

我正在用Java编写应用程序,而我正在使用Guice进行DI。

(确切地说,这是Android和RoboGuice,尽管在这种情况下它可能没什么区别。)

班级设计

这是一款用于保持流行卡片游戏得分的应用程序 - Hearts

游戏由各种连续交易组成,其规则不同。例如。在一次交易中,球员因为夺取红心而受到惩罚,而在另一次交易中,球员因为征服了国王和国王而被罚下。

我的对象设计涉及几个类:

  • Deal用于识别每笔交易

  • ScoreCalculator用于计算罚分(例如,每颗心可能值-2分)。处罚可能因交易而有所不同。

  • ScoreValidator用于验证分数(例如,每个玩家不可能获得4颗心,因为套牌中没有那么多)

如果每个Deal类都有一个对应的ScoreCalculatorScoreValidator,则依赖注入将是微不足道的。

但事实并非如此。计算某些交易的得分可能非常具体(将它们与其他交易区分开来),而对于其余的交易它只是基于惩罚参数(-2或-4等)所取得的卡数量而已。 p>

因此,LotteryDealLotteryCalculator相关联,但NoQueensNoGents都需要一个名为SimpleCalculator的类。

它需要一个整数参数,这是一个乘数(罚分)。

这是我目前的解决方案,其中我将Deal实现为枚举(但我对此并不满意,我想放弃它):

public enum Deal
{
    TakeNothing(-2, PossibleDealResults.fullRange()),
    NoHearts(-2, PossibleDealResults.fullRange()),
    NoQueens(-2, PossibleDealResults.rangeUpTo(4)),
    NoGents(-2, PossibleDealResults.rangeUpTo(8)),
    NoKingOfHearts(-18, PossibleDealResults.rangeUpTo(1)),
    NoLastOne(
        new NoLastOneCalculator(),
        new NoLastOneValidator(new NoLastOneCalculator())),
    Trump1(2, PossibleDealResults.fullRange()),
    Trump2(2, PossibleDealResults.fullRange()),
    Trump3(2, PossibleDealResults.fullRange()),
    Trump4(2, PossibleDealResults.fullRange()),
    Lottery(new LotteryCalculator(), PossibleDealResults.rangeUnique(1, 4));

    protected ScoreCalculator calculator;
    protected PlainScoreValidator validator;

    Deal(int multiplier, PossibleDealResults possibleResults)
    {
        this(new SimpleCalculator(multiplier), possibleResults);
    }

    Deal(ScoreCalculator calculator, PossibleDealResults possibleResults)
    {
        this(calculator, new PlainScoreValidator(possibleResults, calculator));
    }

    Deal(ScoreCalculator calculator, PlainScoreValidator validator)
    {
        Preconditions.checkNotNull(calculator, "calculator");
        Preconditions.checkNotNull(validator, "validator");
        this.calculator = calculator;
        this.validator = validator;
    }
}

我没有删除一些超出此问题范围的复杂性(例如PossibleDealResults类,我没有向您描述),因为它似乎不太相关。

重点是所有依赖项都是硬编码的,正如您所看到的那样,这并不是非常灵活,例如因为游戏有许多不同的变体,并且有各种评分规则。

假设我想使用依赖注入来提供更大的灵活性,甚至可以更容易地在不同的规则集之间切换 - 通过切换到不同的Module以便重新解决依赖关系(如果有的话)需要。

问题出在哪里?

我想我已经掌握了一般如何做到这一点。

我的问题是关注注入SimpleCalculator对象。

我需要-2的{​​{1}}参数,TakeNothingDeal的参数-18

如何使用Guice实现它?

我想保持课程参数化,避免创建NoKingOfHeartsDealMinusTwoSimpleCalculator

我不确定在不滥用框架(或更一般的DI设计指南)的情况下实现这一目标的正确方法是什么。

您尝试了什么?

在实际代码方面并不多。我有点卡住了。

我知道有MinusEighteen...,但在这种情况下我无法看到如何使用它。它需要注释,但是如果使用特定于交易的注释 - 我的意思是,创建一个bindConstant字段,然后使用“注入-2在这里,请”的效果注释它,我真的做了什么?我只是手动回到硬编码依赖项,我不再使用Guice了。

我也读过AssistedInject,但我也无法弄清楚它是如何帮助的。

我不想过分强调这一点,也不想违背框架。什么是正确的方法?很高兴澄清问题是否有些不清楚。

1 个答案:

答案 0 :(得分:1)

实际上,你有很多选择。这是三个:

工厂对象

坦率地说,我不认为这个设计需要Guice来解决这个特殊问题。相反,创建一个简单的界面来填充相关的switch语句:

interface DealFactory {
  ScoreCalculator getFromDeal(Deal deal);
  ScoreValidator getFromDeal(Deal deal);
}

你可能会想,"但是这可以通过伸缩方式对物体起作用!这些方法最好留在Deal。"大多数情况下,你是对的,但OOP(和依赖注入)的一个关键因素是封装不同的。在Deal中静态声明一组规则与您想要的灵活性相反。 (枚举本身很好;无论规则如何,都有有限数量的交易类型。)

在这里,您可以轻松地将DealFactory绑定到一个轻量级对象,该对象为任何给定的Deal提供完全正确的ScoreCalculator和ScoreValidator,并为每组规则编写尽可能多的DealFactory对象。此时,您可以在模块中声明当前正在使用的DealFactory,并将其注入任何您想要的位置。

还要记住,工厂实现可以轻松地与Guice一起使用并注入绑定:

class DealFactoryImpl implements DealFactory {
  @Inject Provider<DefaultScoreCalculator> defaultScoreCalculatorProvider;
  @Inject MultiplierScoreCalculator.Factory multiplerScoreCalculatorFactory;

  @Override public ScoreCalculator getFromDeal(Deal deal) {
    if (TakeNothing.equals(Deal)) {
      return defaultScoreCalculatorProvider.get();
    } else {
      return multiplierScoreCalculatorFactory.create(-2); // assisted inject 
    }
  } /* ... */
}

私人模块

与您类似的问题有时也称为"robot legs" problem,好像您正在编写一个需要在某些树中引用LeftFoot而在其他树中引用RightFoot的常见Leg对象。将(更好的)上述解决方案搁置一秒,您可以设置private modules,这允许您私下绑定事物以仅暴露一些公共依赖项:

// in your module
install(new PrivateModule() {
  @Override public void configure() {
    SimpleCalculator calculator = new SimpleCalculator(-2);
    bind(ScoreCalculator.class).toInstance(calculator);
    bind(ScoreValidator.class).toInstance(
        new PlainScoreValidator(calculator, PossibleDealResults.fullRange());
    expose(Deal.class).annotatedWith(TakeNothing.class); // custom annotation
  }
});

明白我的意思?当然可能,但很多工作的心脏规则。自定义类更适合此特定问题。您需要指定乘数很浅;如果需要P,需要Q,需要R,需要S,需要相同的ScoreCalculator,那么PrivateModule解决方案看起来比在任何地方传递计算器更具吸引力。

@Provides methods

如果你仍想在Guice中解决这个问题,但是你不想编写很多私有模块,每个模块都会暴露一个绑定,你可以用@Provides方法自行解决问题。

// in your module adjacent to configure()
@Provides @TakeNothing Deal anyMethodNameWorks(/* dependencies here */) {
  SimpleCalculator calculator = new SimpleCalculator(-2);
  ScoreValidator validator = new PlainScoreValidator(calculator,
      PossibleDealResults.fullRange());
  return new Deal(calculator, validator);
}

同样,这将为每种类型的交易创建一个绑定,这可能是一个坏主意,但它比上面的更轻量级。在某种程度上,你在Guice的工作 - 创建对象 - 我的意思是 - 但是如果你需要Guice可以提供的任何依赖,你可以将它们作为方法参数注入@Provides方法本身

相关问题