Home > Software engineering >  How To Refactor Switch Statement To Flexible Finite State Machine
How To Refactor Switch Statement To Flexible Finite State Machine

Time:03-25

In a manufacturing environment, for a specific process, there are fairly straightforward C# Winforms Desktop applications that involve enum-based state machines running on infinite loop, following a structure similar to below (state names kept general for the sake of example).

switch(state):

    case STATE0:
       // code (ui update, business logic, and/or database/device communication)
       if (...)
          state = STATE1; // goes to next state

       break;

    case STATE1:
       // code (ui update, business logic, and/or database/device communication)
       if(...)
          state = STATE2; // goes to next state

       break;

    case STATE2:
       // code (ui update, business logic, and/or database/device communication)
       if(...)
          state = STATE1; // goes back to STATE1
    
       else if (...)
          state = STATE3; // goes to next state

       break;
    
    case STATE3:
       // code (ui update, business logic, and/or database/device communication)
       if (...)
          state = STATE0; // goes back to STATE0

       break;

There are many products that go through this process. There are many states across products that are almost the exact the same (e.g. state0). But product-specific logic within the states are slightly different across products.

Is there a way to refactor the above switch statement to a more cleaner, flexible finite state machine, that can account for variation within the states?

CodePudding user response:

If you have multiple states and it is necessary to change behaviour based on state, then we can use strategy pattern combined with factory pattern.

At first, we need to declare states:

public enum State
{
    State_1,
    State_2,
    State_3,
    State_4
}

Then based on state we need to choose some behaviour. We can put behaviour in class. Let's call it product. So we need to create some abstract class and put all duplicated logic in this abstract class. Then if behaviour of some classes is different or we want to add new behavior, then we will create derived classes. By adding new classes we keep to Open Closed principle.

public abstract class Product
{
    public string Name { get; set; }

    public decimal Price { get; set; }


    public string TheSameBehaviorForAllProducts() 
    {
        return "TheSameHehaviorForAllProducts";
    }


    public virtual string BehaviorThatCanBeOverridenInDerivedClasses()
    {
        return "TheSameHehaviorForAllProducts";
    }
}

and derived classes of Product:

public class Product_A : Product
{
    public override string BehaviorThatCanBeOverridenInDerivedClasses()
    {
        return "New Behavior Here";
    }
}

public class Product_B : Product
{
    public override string BehaviorThatCanBeOverridenInDerivedClasses()
    {
        return "New Behavior Here";
    }
}

public class Product_C : Product
{
    public override string BehaviorThatCanBeOverridenInDerivedClasses()
    {
        return "New Behavior Here";
    }
}

Then we can create a something like mapper which maps State to Product. It is also can be considered as Factory pattern:

public class StateToProduct 
{
    public Dictionary<State, Product> ProductByState = new()
        {
            { State.A, new Product_A() },
            { State.B, new Product_B() },
            { State.C, new Product_C() }
        };
}

and our state machine:

public class StateMachine
{
    // Here your logic to get state
    public State GetState() 
    {
        return State.A;
    }
}

Then we can run big system like this:

public class SomeBigSystem
{
    public void Run()
    {
        StateMachine stateMachine = new();
        StateToProduct stateToProduct = new();
        Product product = stateToProduct.ProductByState[stateMachine.GetState()];

        // executing some business logic
        product.BehaviorThatCanBeOverridenInDerivedClasses();
    }
}   

So we've created simple classes that are testable and we used here Strategy pattern.

I highly recommend you to read about SOLID and techniques of refactoring

  • Related