Question

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));
    }
}
Was it helpful?

Solution

Should I just make these constructor parameters an automatic property

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:

public interface IBucketActionsHandlerFactory
{
    IBucketActionsHandler Create(
        Bucket largeBucket,
        Bucket smallBucket,
        int amountToRetrieve);
}

You can inject this factory into the service that needs it, and let that service supply the proper context variables:

public class SomeService
{
    private IBucketActionsHandlerFactory factory;
    private IBucketRepository repository;

    public SomeService(IBucketActionsHandlerFactory factory,
        IBucketRepository repository)
    {
        this.factory = factory;
        this.repository = repository;
    }

    public void Handle(int amountToRetrieve)
    {
        var largeBucket = this.repository.GetById(LargeBucketId);
        var smallBucket = this.repository.GetById(SmallBucketId);

        var handler = this.factory.Create(largeBucket, smallBucket,
            amountToRetrieve);

        handler.CalculateSteps();
    }
}

The factory will be in control of creating new IBucketActionsHandler implementations:

public class BucketActionsHandlerFactory
    : IBucketActionsHandlerFactory
{
    private Container container;

    public class BucketActionsHandlerFactory(
        Container container)
    {
        this.container = container;
    }

    public IBucketActionsHandler Create(
        Bucket largeBucket, Bucket smallBucket,
        int amountToRetrieve)
    {
        return new BucketActionsHandler(
            largeBucket, smallBucket, amountToRetrieve,
            this.container.Get<IActionLogger>());
    }
}

Your BucketActionsHandlerFactory should 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 BucketActionsHandler within its current context.

Alternatively, you can supply the largeBucket, smallBucket, and amountToRetrieve variables to the CalculateSteps method. This allows you to remove the need to a factory:

public class BucketActionsContext
{
    public Bucket LargeBucket { get; set; }
    public Bucket SmallBucket { get; set; }
    public int AmountToRetrieve { get; set; }
}

public class BucketActionsHandler : IBucketActionsHandler
{
    private IActionLogger _actionLogger;

    public BucketActionsHandler(IActionLogger actionLogger)
    {
        _actionLogger = actionLogger;
    }

    public void CalculateSteps(
        BucketActionsContext context)
    {
        IAction nextAction;
        do
        {
            nextAction = this.GetNextAction(context);

            if (nextAction == null)
            {
                throw new InvalidOperationException(
                    "No valid action available");
            }

            nextAction.Execute();
        } 
        while(!(nextAction is LastAction));
    }

    private IAction GetNextAction(
        BucketActionsContext context)
    {
        return (
            from action in this.GetActions(context)
            where action.SatisfiedCondition()
            select action)
            .FirstOrDefault();
    }

    private IEnumerable<IAction> GetActions(
        BucketActionsContext context)
    {
        Bucket largeBucket = context.LargeBucket;
        Bucket smallBucket = context.SmallBucket;
        int amountToRetrieve = context.AmountToRetrieve;

        yield return new LastAction(largeBucket, smallBucket, amountToRetrieve);
        yield return new EmptySmallerBucketAction(largeBucket, smallBucket, amountToRetrieve);
        yield return new EmptyLargeBucketAction(largeBucket, smallBucket, amountToRetrieve);
        yield return new FillLargeBucketAction(largeBucket, smallBucket, amountToRetrieve);
        yield return new FillSmallBucketAction(largeBucket, smallBucket, amountToRetrieve);
        yield return new TransferToLargeBucketAction(largeBucket, smallBucket, amountToRetrieve);
        yield return new TransferToSmallBucketAction(largeBucket, smallBucket, amountToRetrieve);    
    }
}

OTHER TIPS

You can either manually resolve IActionLogger and manually inject it into constructor while passing other parameters appropriately
or
you can make dependency override (that's how it's called in Unity) when resolving BucketActionsHandler which will force it to use passed values as injected dependencies.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top