2

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?

1 Answers1

0

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

StepUp
  • 36,391
  • 15
  • 88
  • 148
  • Looks like mapped state to product as 1-to-1. But this is not the case - states are merely steps in the process. All products have multiple steps (each product has own FSM). Some products will have the same steps, but logic within the step can be different. This is Strategy pattern; the state is a particular step which is common between some products, but based on product, executes product-specific business logic in that step. In my pseudocode, this is IBusinessLogic1 injected into State2. Products could have different steps as well (one product FSM has steps another product does not). – user18463824 Mar 21 '22 at 00:06
  • Thank you for your answer though. Please let me know if there is anything that is unclear in my original post based on my comment above, and I can try to clarify. I actually have been reading extensively into SOLID principles and design patterns (and actually refactoring.guru is one source I constantly revisit). – user18463824 Mar 21 '22 at 00:16
  • @user18463824 When you have `switch` or multiple `if else` statements, then you can refactor it to like in my reply above. So if your some of subclasses have logic with `switch`, then you can [divide this logic into subclasses or use state or strategy pattern](https://refactoring.guru/smells/switch-statements) – StepUp Mar 21 '22 at 06:48
  • 1
    Yes, I believe I did refactor the switch as per my second code snippet in my original post, to use Strategy pattern and separate classes encapsulating each state as well. The problem isn't the switch - the problem is actually the FSM building; I'm not sure how to "build" the FSM in a clean way that avoids duplication, within a single solution. I've edited my original post with an "EDIT 01" section at the bottom, to explain this problem through some examples. – user18463824 Mar 22 '22 at 16:17
  • @user18463824 yeah, you did a great job. You made the first step of refactoring. Now you can go further. As a good practice, it is better to ask a new question as your `EDIT 1` is big. In addition, new post will get more attention from people. If my reply helps to you, then you can upvote or mark my reply as an answer to simplify the search of future users. [How does accepting an answer work](https://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work)? – StepUp Mar 22 '22 at 19:19
  • Sorry if I was unclear - my original question was already asking about the FSM building (the title of the post is asking for architecture/design pattern for "varying FSM within single application). I only included the switch code to give some background... And to clarify the original question, I added the EDIT 1. Should I edit the question to remove the switch, and keep it only relevant to the FSM? I believe your answer was fine for the question of "how to refactor switch statement to FSM", but that was actually not my original question. – user18463824 Mar 22 '22 at 21:15
  • @user18463824 I've seen your `EDIT_01` again and, in my view, there is no code duplication. As all methods are necessary and you are just calling this methods, but not adding new duplicate implementation in new methods. Calling methods with different parameters are not code duplication. – StepUp Mar 23 '22 at 10:05
  • Thank you for taking another look, I appreciate your thoughts. However, I'd have to disagree - but maybe I just haven't explained it clearly. I agree with your previous point that my post is very long, maybe all the background was unnecessary and clutters my intended question and demonstration of the problem. Would it be okay if I edit my question to be like "How to refactor switch statement to FSM" (and take out most of the text and also take out the EDIT 01), then I can accept your answer? As you suggested, then I can open a new question that focuses more on the FSM. – user18463824 Mar 24 '22 at 00:28
  • @user18463824 sure, it would be better to do as it is good practise to have one question per one post. And try to explain what you consider code duplication is with code examples. And how code duplication is run? And try to make post a little bit shorter. Thanks! :) – StepUp Mar 24 '22 at 06:24