Search code examples
c#.netdesign-patternsdependency-injectionstate-pattern

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


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.


Solution

  • 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();
        }
    }