VB.NET: Using FiniteStateMachine instead of nested if

This blog contains a VB.NET sample using a FiniteStateMachine  for encapsulating nested if statements.

Imports System.Collections.Generic

Namespace SomApp

    'http://stackoverflow.com/questions/5923767/simple-state-machine-example-in-c
    Public Class SomeFiniteStateMachine

        Public Enum ProcessState
            'Waiting for question states
            StartState1AwaitingQuestion1WhoHasMostMembers
            State2AwaitingQuestion2DoesMembersMatch
            'Waiting for action states
            State3AwaitingAction1RemoveFromY
            State4AwaitingAction2AddFromX
            'Done state
            EndState99Done
        End Enum

        Class StateTransition

            Private CurrentState As ProcessState
            Private Command As AnswersAndActions

            Public Sub New(ByVal currentState As ProcessState, ByVal command As AnswersAndActions)
                MyBase.New()
                Me.CurrentState = currentState
                Me.Command = command
            End Sub

            ''' <summary>
            ''' For Dictionary Contains etc ...
            ''' http://stackoverflow.com/questions/36471007/implement-containskey-when-key-is-an-object
            ''' </summary>
            ''' <returns></returns>
            ''' <remarks></remarks>
            Public Overrides Function GetHashCode() As Integer
                Return Me.Command Xor Me.CurrentState 
            End Function
            ''' <summary>
            ''' For Dictionary Contains etc ...
            ''' http://stackoverflow.com/questions/36471007/implement-containskey-when-key-is-an-object
            ''' </summary>
            ''' <param name="obj"></param>
            ''' <returns></returns>
            ''' <remarks></remarks>
            Public Overrides Function Equals(obj As Object) As Boolean
                Dim my As StateTransition = DirectCast(obj, StateTransition)
                Return my = Me
            End Function

            Shared Operator =(ByVal v As StateTransition, ByVal w As StateTransition) As Boolean
                Return v.Command = w.Command AndAlso v.CurrentState  = w.CurrentState 
            End Operator
            Shared Operator <>(ByVal v As StateTransition, ByVal w As StateTransition) As Boolean
                Return Not (v = w)
            End Operator
        End Class

        'Private transitions As Dictionary(Of StateCommandTransition, ProcessState)
        Private transitions As Dictionary(Of StateTransition, ProcessState)

        Public Property CurrentState As ProcessState
            Get
            End Get
            Set(value As ProcessState)
            End Set
        End Property

        Public Sub New()
            MyBase.New()
            Me.CurrentState = ProcessState.StartState1AwaitingQuestion1WhoHasMostMembers

            Me.transitions = New Dictionary(Of StateTransition, ProcessState)
            Me.transitions.Add(New StateTransition(ProcessState.StartState1AwaitingQuestion1WhoHasMostMembers, AnswersAndActions.A1ACountMatch), ProcessState.State2AwaitingQuestion2DoesMembersMatch)
            Me.transitions.Add(New StateTransition(ProcessState.StartState1AwaitingQuestion1WhoHasMostMembers, AnswersAndActions.A1BCountDonotMatchMostInX), ProcessState.State4AwaitingAction2AddFromX)
            Me.transitions.Add(New StateTransition(ProcessState.StartState1AwaitingQuestion1WhoHasMostMembers, AnswersAndActions.A1CCountDonotMatchMostInY), ProcessState.State3AwaitingAction1RemoveFromY)
            Me.transitions.Add(New StateTransition(ProcessState.State3AwaitingAction1RemoveFromY, AnswersAndActions.Did1RemovedFromY), ProcessState.StartState1AwaitingQuestion1WhoHasMostMembers)
            Me.transitions.Add(New StateTransition(ProcessState.State4AwaitingAction2AddFromX, AnswersAndActions.Did2AddedFromX), ProcessState.StartState1AwaitingQuestion1WhoHasMostMembers)
            Me.transitions.Add(New StateTransition(ProcessState.State2AwaitingQuestion2DoesMembersMatch, AnswersAndActions.A2BMembersDonotMatch), ProcessState.State3AwaitingAction1RemoveFromY)
            Me.transitions.Add(New StateTransition(ProcessState.State2AwaitingQuestion2DoesMembersMatch, AnswersAndActions.A2AMembersMatch), ProcessState.EndState99Done)

        End Sub

        Public Function MoveNext(ByVal command As AnswersAndActions) As ProcessState
            CurrentState = GetNext(command)
            Return CurrentState
        End Function

        Private Function GetNext(ByVal command As AnswersAndActions) As ProcessState
            Dim transition As StateTransition = New StateTransition(CurrentState, command)
            Dim nextState As ProcessState
            If Not transitions.TryGetValue(transition, nextState) Then
                Throw New Exception(("Invalid transition: " + (CurrentState + (" -> " + command))))
            End If

            Return nextState
        End Function
    End Class

    Public Enum AnswersAndActions
        'Answers from Question1
        A1ACountMatch
        A1BCountDonotMatchMostInX
        A1CCountDonotMatchMostInY
        'Answers from Question2
        A2AMembersMatch
        A2BMembersDonotMatch
        'Actions done
        Did1RemovedFromY
        Did2AddedFromX
        'Generic Invalid answer
        A99AnswerUnknown
    End Enum

    Public Class SomeCrudService

        Public Function Question1WhoHasMostMembers(questionDetail As String) As AnswersAndActions
            Dim response As AnswersAndActions = AnswersAndActions.A99AnswerUnknown
            If questionDetail = "55 bananas" Then
                response = AnswersAndActions.A1CCountDonotMatchMostInY
            ElseIf questionDetail = "77 nuts" Then
                response = AnswersAndActions.A1BCountDonotMatchMostInX
            Else
                response = AnswersAndActions.A1ACountMatch
            End If
            Return response
        End Function

        Public Function Question2DoesMembersMatch(questionDetail As String) As AnswersAndActions
            Dim response As AnswersAndActions = AnswersAndActions.A99AnswerUnknown
            If questionDetail = "bla" Then
                response = AnswersAndActions.A2BMembersDonotMatch
            Else
                response = AnswersAndActions.A2AMembersMatch
            End If
            Return response
        End Function

        Public Function Act1RemoveFromY(toRemove As String) As AnswersAndActions
            'do something

            Return AnswersAndActions.Did1RemovedFromY
        End Function

        Public Function Act2AddFromX(toAdd As String) As AnswersAndActions
            'do something

            Return AnswersAndActions.Did2AddedFromX
        End Function

    End Class

    Public Class Program

        Private Shared Sub Main(ByVal args() As String)
            Dim stateMachine As SomeFiniteStateMachine = New SomeFiniteStateMachine
            Console.WriteLine(("Current State = " + stateMachine.CurrentState)) 'Is StateAwaitingQuestion1WhoHasMost

            Dim crudSvc As SomeCrudService = New SomeCrudService()
            Dim svcAnswersAndActions As AnswersAndActions

            'Loop statemachine until state is ProcessState.State99Done
            While Not stateMachine.CurrentState = SomeFiniteStateMachine.ProcessState.EndState99Done
                Select Case stateMachine.CurrentState
                    Case SomeFiniteStateMachine.ProcessState.StartState1AwaitingQuestion1WhoHasMostMembers
                        'Get answer of Question1
                        svcAnswersAndActions = crudSvc.Question1WhoHasMostMembers("")
                        stateMachine.MoveNext(svcAnswersAndActions)
                    Case SomeFiniteStateMachine.ProcessState.State2AwaitingQuestion2DoesMembersMatch
                        'Get answer of Question1
                        svcAnswersAndActions = crudSvc.Question2DoesMembersMatch("")
                        stateMachine.MoveNext(svcAnswersAndActions)
                    Case SomeFiniteStateMachine.ProcessState.State3AwaitingAction1RemoveFromY
                        'Act
                        svcAnswersAndActions = crudSvc.Act1RemoveFromY("Remove G")
                        stateMachine.MoveNext(svcAnswersAndActions)
                    Case SomeFiniteStateMachine.ProcessState.State4AwaitingAction2AddFromX
                        'Act
                        svcAnswersAndActions = crudSvc.Act2AddFromX("Add Z")
                        stateMachine.MoveNext(svcAnswersAndActions)
                    Case Else
                        Console.WriteLine(("Current State = " + stateMachine.CurrentState))
                        Throw New Exception("Unexpected State: " + stateMachine.CurrentState)
                End Select

                'Verify new state (next question or action)
                Console.WriteLine(("Current State = " + stateMachine.CurrentState))

            End While

            Console.ReadLine()
        End Sub
    End Class

End Namespace

Thanks to

http://stackoverflow.com/users/40516/juliet

for the C# sample

http://stackoverflow.com/questions/5923767/simple-state-machine-example-in-c

Then End.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: