.NET - It is possible to use DI with the State Pattern?

643 Views Asked by At

I'm studying design patterns in .NET and currently i'm trying to implement the State Pattern. But today i got to a problem and i can't figure out how to fix this situation.

I have some state classes, all of them implementing the state interface. One of the last states should connect to a data base, through a service injected by the .NET API Startup class, to persist the data and finish up the process.

The problem is... because of the dependency injection that i need to be in the final state, i can't instanciate this state object to progress to this point. I don't know how to continue from there. I don't know if i'm using the pattern wrong or if the use of dependency injection in this pattern is the problem. I can't give all the details of the problem because my studie's project is a little big mess at this moment, so i made a quick mimic of the structure i'm trying to build in my application.

States interface and the OperatingClass who will execute the state behaviour:

    public interface IOperationState
    {
        public int ExecuteOperation(OperatingClass operatingClass);
    }
    public class OperatingClass
    {
        public IOperationState OperationState { get; set; }
        public int id { get; set; }
        public double value { get; set; }

        public OperatingClass(int id)  //constructor
        {
            this.id = id;
            value = 0;
            OperationState = new StartingState();
        }

        public int Execute()
        {
            return OperationState.ExecuteOperation(this);
        }
    }

Main Service: is the service who my controller calls after receive the API Post Method:

    public class MainService
    {
        public int ExecuteFullOperation(int id)
        {
            //Receives an id and execute the state transition till the end;
            var operatingClass = new OperatingClass(id);
            return operatingClass.Execute();
        }
    }

The classes who represents the states and do the respective actions:

    public class StartingState : IOperationState
    {
        
        public int ExecuteOperation(OperatingClass operatingClass)
        {
            // Do something...
            operatingClass.OperationState = new MiddleState();
            return operatingClass.Execute();
        }
    }
    public class MiddleState : IOperationState
    {
        public int ExecuteOperation(OperatingClass operatingClass)
        {
            //Do something with the value... let's supose the result is 123, but it does not matter rn;
            operatingClass.value = 123;

            //Here is the problem: FinalState needs the PersistenceService, who
            //receives a injected class to acess the database;
            operatingClass.OperationState = new FinalState();

            //I want to execute it and return the sucess or failure of the persistence.
            return operatingClass.Execute();
        }
    }
    public class FinalState : IOperationState
    {
        private readonly IPersistenceService PersistenceService;
        public FinalState(IPersistenceService persistenceService)
        {
            PersistenceService = persistenceService;
        }

        public int ExecuteOperation(OperatingClass operatingClass)
        {
            return PersistenceService.PersistData(operatingClass.id, operatingClass.value) ? 200 : 503;
        }
    }

Additional info: i made the PersistenceService be injected in the Startup.cs as a Transient (i dont know how to make it in another way at this moment).

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IPersistenceService, PersistenceService>();
            // Irrelevant configurations for the question.
            services.AddControllers();
        }

Please, help me if you can. I'm having a hard time trying to figure it out by myself. Thank you for your patience and for your time reading it.

1

There are 1 best solutions below

4
StepUp On BEST ANSWER

Firstly, we need some simple factory which will supply all necessary dependencies by their type. So let's create types for states:

public enum StateType
{
    Start,
    Middle,
    Final
}

And simple factory:

public class StateFactory
{
    private Dictionary<StateType, IOperationState> _stateByType;


    // you can inject these dependencies through DI like that:
    // public StateFactory(StartingState startingState, 
    //     MiddleState middleState, FinalState finalState, 
    //     PersistenceService persistenceService)
    public StateFactory()
    {
        _stateByType = new Dictionary<StateType, IOperationState>()
        {
            { StateType.Start, new StartingState(this) },
            { StateType.Middle, new MiddleState(this) },
            { StateType.Final, new FinalState(new PersistenceService()) }
        };
    }

    public IOperationState GetByType(StateType stateType) => 
        _stateByType[stateType];
}

Then we should register all our dependencies:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IPersistenceService, PersistenceService>();
    
    services.AddTransient<StartingState>();
    services.AddTransient<MiddleState>();
    services.AddTransient<FinalState>();
    
    services.AddTransient<MainService>();
    services.AddTransient<OperatingClass>();
    services.AddTransient<PersistenceService>();
    services.AddTransient<StateFactory>();

}

Our states would look like this:

public class StartingState : IOperationState
{
    private StateFactory _factory;

    public StartingState(StateFactory stateFactory)
    {
        _factory = stateFactory;
    }

    public int ExecuteOperation(OperatingClass operatingClass)
    {
        // Do something...
        // operatingClass.OperationState = new MiddleState();
        operatingClass.OperationState = _factory.GetByType(StateType.Middle);
        return operatingClass.Execute();
    }
}

And MiddleState would look like this:

public class MiddleState : IOperationState
{
    private StateFactory _factory;

    public MiddleState(StateFactory stateFactory)
    {
        _factory = stateFactory;
    }

    public int ExecuteOperation(OperatingClass operatingClass)
    {
        //Do something with the value... let's supose the result is 123, 
        // but it does not matter rn;
        operatingClass.value = 123;

        //Here is the problem: FinalState needs the PersistenceService, who
        //receives a injected class to acess the database;
        operatingClass.OperationState = _factory.GetByType(StateType.Final);

        //I want to execute it and return the sucess or failure of the persistence.
        return operatingClass.Execute();
    }
}

And Final state should look like this:

 public class FinalState : IOperationState
{
    private readonly IPersistenceService _persistenceService;

    public FinalState(IPersistenceService persistenceService)
    {
        _persistenceService = persistenceService;
    }

    public int ExecuteOperation(OperatingClass operatingClass)
    {
        return _persistenceService
            .PersistData(operatingClass.id, operatingClass.value) 
                ? 200 
                : 503;
    }
}

And other classes sush as OperatingClass would use StateFactory too:

public class OperatingClass { public IOperationState OperationState { get; set; } public int id { get; set; } public double value { get; set; }

    public OperatingClass(int id, StateFactory stateFactory)  //constructor
    {
        this.id = id;
        value = 0;
        // OperationState = new StartingState();
        OperationState = stateFactory.GetByType(StateType.Start);
    }

    public int Execute()
    {
        return OperationState.ExecuteOperation(this);
    }
}

And it is necessary to create concrete example of PersistenceService:

public interface IPersistenceService
{
    bool PersistData(int id, double value);
}

public class PersistenceService : IPersistenceService
{
    public bool PersistData(int id, double value)
    {
        throw new NotImplementedException();
    }
}