You have a lot of options, actually. Here are three:
Factory object
Frankly, I don't think this design needs Guice for this particular problem. Instead, create a simple interface to populate with relevant switch
statements:
interface DealFactory {
ScoreCalculator getFromDeal(Deal deal);
ScoreValidator getFromDeal(Deal deal);
}
You might be thinking, "But that works on objects telescopically! Those methods would be better left on Deal." You'd be right, mostly, but one key factor of OOP (and Dependency Injection) is to encapsulate what varies. Having a single set of rules declared statically in Deal is the opposite of the flexibility you want. (The enum itself is fine; there are a finite number of deal types regardless of the rules in play.)
Here, you could easily bind the DealFactory to some lightweight object that provides exactly the right ScoreCalculator and ScoreValidator for any given Deal, and write as many DealFactory objects as you'd like for each set of rules. At that point you can declare the currently-in-play DealFactory in a module and inject it wherever you want.
Also bear in mind that a factory implementation could easily work with Guice and injected bindings:
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
}
} /* ... */
}
Private modules
A similar problem to yours is sometimes known as the "robot legs" problem, as if you're writing a common Leg object that needs to refer to a LeftFoot in some trees and a RightFoot in others. Setting aside the (better) above solution for a second, you can set up private modules, which allow you bind things privately to expose only a few public dependencies:
// 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
}
});
See what I mean? Certainly possible, but a lot of work for Hearts rules. A custom class is a better match for this particular problem. Your need to specify multipliers is shallow; if Deal needed P, which needed Q, which needed R, which needs S, which needs the same ScoreCalculator, then a PrivateModule solution looks much more appealing than passing your calculator around everywhere.
@Provides methods
If you still want to solve this in Guice, but you don't want to write a lot of private modules that expose one binding each, you can take matters into your own hands with @Provides
methods.
// 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);
}
Again, this will create a binding for every type of Deal, which is probably a bad idea, but it's a bit more lightweight than the above. To some degree you're doing Guice's job for it—creating objects, I mean—but should you need any dependencies Guice can provide, you can inject them as method parameters on the @Provides
method itself.