Background
I'm writing an application in Java and I'm using Guice for DI.
(It's Android and RoboGuice to be exact, although it probably makes no difference in this context).
Class design
It's an app for keeping score for a popular cardgame - Hearts.
The game consists of various consecutive deals, whose rules differ. Eg. in one deal players are penalized for taking Hearts, in another - for taking Jokers and Kings.
My object design involves several classes:
Deal
for identifying each dealScoreCalculator
for calculating penalties (eg. each Heart may be worth -2 points). Penalties may vary from deal to deal.ScoreValidator
for validating the scores (for instance, it's not possible for each player to take 4 Hearts, because there isn't as many in the deck)
If each Deal
class had one corresponding ScoreCalculator
and ScoreValidator
, dependency injection would be trivial.
But this is not the case. Calculating score for some of the deals can be very specific (distinguishing them from the others), while for the rest it's based on nothing more but multiplying the number of taken cards by the penalty parameter (-2 or -4 etc.)
So, LotteryDeal
is associated with a LotteryCalculator
, but NoQueens
and NoGents
both require a class I named SimpleCalculator
.
It takes a single integer parameter, which is a multiplier (penalty score).
This is my current solution, in which I implemented Deal
as an enum (but I'm not happy with it and I want to drop it):
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;
}
}
I'm not removing some complexities that are out of the scope of this question (such as the PossibleDealResults
class, which I did not describe to you), as it doesn't seem to be very relevant.
The main point is that all dependencies are hard-coded, as you can see, which is not really flexible, for example because there are many different variations of the game, with various scoring rules.
Let's say I'd like to use dependency injection to allow for more flexibility and perhaps even switching between different rules sets more easily - by switching to a different Module
in order to re-resolve dependencies if there is a need.
Where's the problem?
I think I have some grasp on how to do it in general.
My question concerns injecting the SimpleCalculator
object.
I'd need it with a parameter of -2
for TakeNothingDeal
, but -18
for the NoKingOfHeartsDeal
.
How to achieve it with Guice?
I'd like to keep the class parameterized and avoid creating a MinusTwoSimpleCalculator
and a MinusEighteen...
one.
I'm not really sure what's the proper way to achieve that, without abusing the framework (or more general DI design guidelines).
What have you tried?
Not much in terms of actual code. I'm a bit stuck.
I know there's bindConstant
, but I can't see how I could make use of it in this case. It requires annotations, but if use Deal-specific annotations - I mean, create a Deal.multiplier
field and then annotate it with something to the effect of "inject -2 here, please", what did I really do? I just went back to hard-coding dependencies manually and I'm not really using Guice anymore.
I read about AssistedInject, too, but I can't figure out how it could be of help here, either.
I don't want to overengineer this nor to work against the framework. What's the correct approach? Happy to clarify if the problem is somehow unclear.