I would like to inject the constructor parameter IActionLogger actionLogger, but want the other parameters largeBucket, smallBucket and amountToRetrieve are context-sensitive (not sure if that’s the right term).
Question:
Should I just make these constructor parameters an automatic property and just leave the IActionLogger actionLogger parameter in the constructor?
Basically, the calculations will vary based on the largeBucket, smallBucket and amountToRetrieve variables?
I put these variables in the constructor because I need to do some setup beforehand.
public class BucketActionsHandler : IBucketActionsHandler
{
private List<IAction> _actions = new List<IAction>();
private Bucket _largeBucket;
private Bucket _smallBucket;
private IActionLogger _actionLogger;
private int _amountToRetrieve;
public BucketActionsHandler(Bucket largeBucket, Bucket smallBucket, int amountToRetrieve, IActionLogger actionLogger)
{
_largeBucket = largeBucket;
_smallBucket = smallBucket;
_amountToRetrieve = amountToRetrieve;
_actionLogger = actionLogger;
_actions.Add(new LastAction(largeBucket, smallBucket, amountToRetrieve));
_actions.Add(new EmptySmallerBucketAction(largeBucket, smallBucket, amountToRetrieve));
_actions.Add(new EmptyLargeBucketAction(largeBucket, smallBucket, amountToRetrieve));
_actions.Add(new FillLargeBucketAction(largeBucket, smallBucket, amountToRetrieve));
_actions.Add(new FillSmallBucketAction(largeBucket, smallBucket, amountToRetrieve));
_actions.Add(new TransferToLargeBucketAction(largeBucket, smallBucket, amountToRetrieve));
_actions.Add(new TransferToSmallBucketAction(largeBucket, smallBucket, amountToRetrieve));
}
private IAction GetNextAction()
{
foreach (var action in _actions)
{
if (action.SatisfiedCondition())
{
return action;
}
}
return null;
}
public void CalculateSteps()
{
IAction nextAction;
do
{
nextAction = GetNextAction();
nextAction.Execute();
if (nextAction == null)
{
throw new InvalidOperationException("No valid action available");
}
} while(!(nextAction is LastAction));
}
}
No, because this would allow you to change this service after it is injected or created, which is a bad thing, because services should be stateless, or at least their internal state changes should have no effect on the correctness of the application. When you change the state of the service, the application code forces this service to be a transient (new instance should be injected each time it is requested), while the application shouldn’t care. This moves the control and decision of what lifetime services have out of the Composition Root (the start-up path of the application), which hinders maintainability.
Instead, use a factory:
You can inject this factory into the service that needs it, and let that service supply the proper context variables:
The factory will be in control of creating new
IBucketActionsHandlerimplementations:Your
BucketActionsHandlerFactoryshould be part of the Composition Root, and in this case it is fine to inject a container / kernel into this factory (it is part of the DI infrastructure).This way the application is ignorant about what type of handler it gets, but is still able to get a
BucketActionsHandlerwithin its current context.Alternatively, you can supply the
largeBucket,smallBucket, andamountToRetrievevariables to theCalculateStepsmethod. This allows you to remove the need to a factory: