Introduction
Building state machines from scratch can be rewarding, but using a dedicated library like Stateless can streamline the process and simplify complex state transitions. Stateless is a hierarchical state machine library in C#, developed by Nicholas Blumhardt, that provides an easy-to-use interface for defining states, triggers, and conditions.
In this article, we'll set up a state machine in a scenario inspired by reproductive health, which includes various states a person might experience throughout different stages of life. This example will demonstrate the power of the Stateless library in handling complex state transitions and conditional actions.
Why Use Stateless?
The Stateless library provides a robust and extensible way to manage state machines with:
- Declarative Syntax: Easily define states, triggers, and transitions.
- Conditional Transitions: Apply conditions to control state changes.
- Hierarchical State Management: Organize states and manage complex transitions efficiently.
Step 1: Setting Up the Stateless Library
To use Stateless, start by installing the package via NuGet:
dotnet add package Stateless
or in the NuGet Package Manager Console:
Install-Package Stateless
Once installed, we can begin defining our states and triggers.
Step 2: Define States and Triggers
Let’s define two enums: HealthState
for different life stages and Activity
for actions or events that trigger state transitions.
using Stateless;
namespace HealthStateMachineExample
{
// Define the states related to reproductive health
public enum HealthState
{
NonReproductive,
Reproductive,
Pregnant
}
// Define activities that can trigger state transitions
public enum Activity
{
ReachPuberty,
HaveUnprotectedSex,
GiveBirth,
HaveAbortion,
UndergoSurgery
}
}
Explanation:
-
HealthState Enum:
-
NonReproductive
: The individual is not yet capable of reproduction. -
Reproductive
: The individual is capable of reproduction. -
Pregnant
: The individual is currently pregnant.
-
-
Activity Enum:
-
ReachPuberty
: Transition to reproductive state. -
HaveUnprotectedSex
: May lead to pregnancy if conditions are met. -
GiveBirth
: Transition back to reproductive after pregnancy. -
HaveAbortion
: Return to reproductive state from pregnancy. -
UndergoSurgery
: Transition to non-reproductive by removing reproductive capability.
-
Step 3: Initialize the State Machine
Let’s initialize our state machine with Stateless by defining the initial state and setting up triggers for state transitions.
public class HealthStateMachine
{
private readonly StateMachine<HealthState, Activity> _stateMachine;
public bool ParentsNotWatching { get; set; } = true; // Conditional property
public HealthStateMachine()
{
// Initialize the state machine starting in the NonReproductive state
_stateMachine = new StateMachine<HealthState, Activity>(HealthState.NonReproductive);
ConfigureStateMachine();
}
private void ConfigureStateMachine()
{
// Configure NonReproductive state
_stateMachine.Configure(HealthState.NonReproductive)
.Permit(Activity.ReachPuberty, HealthState.Reproductive);
// Configure Reproductive state
_stateMachine.Configure(HealthState.Reproductive)
.PermitIf(Activity.HaveUnprotectedSex, HealthState.Pregnant, () => ParentsNotWatching)
.Permit(Activity.UndergoSurgery, HealthState.NonReproductive);
// Configure Pregnant state
_stateMachine.Configure(HealthState.Pregnant)
.Permit(Activity.GiveBirth, HealthState.Reproductive)
.Permit(Activity.HaveAbortion, HealthState.Reproductive);
}
}
Explanation:
-
Initialization:
- We define a StateMachine object,
_stateMachine
, withHealthState
as the state type andActivity
as the trigger type. - The initial state is set to NonReproductive.
- We define a StateMachine object,
-
Conditional Property:
-
ParentsNotWatching
simulates a real-life condition for allowing state transitions (e.g., pregnancy only if parents aren’t watching).
-
-
ConfigureStateMachine Method:
-
NonReproductive State: The individual can transition to
Reproductive
state by triggeringReachPuberty
. -
Reproductive State:
- Triggers a conditional transition to
Pregnant
state ifHaveUnprotectedSex
is called andParentsNotWatching
is true. - Allows transition to
NonReproductive
ifUndergoSurgery
is triggered.
- Triggers a conditional transition to
-
Pregnant State: Transition to
Reproductive
state by eitherGiveBirth
orHaveAbortion
.
-
NonReproductive State: The individual can transition to
Step 4: Testing the State Machine
Now let’s simulate some transitions to see how the state machine responds to different activities.
class Program
{
static void Main(string[] args)
{
var healthMachine = new HealthStateMachine();
Console.WriteLine($"Initial State: {healthMachine.CurrentState}");
// Transition from NonReproductive to Reproductive
healthMachine.PerformTransition(Activity.ReachPuberty);
Console.WriteLine($"After Puberty: {healthMachine.CurrentState}");
// Attempt to transition to Pregnant (will succeed if ParentsNotWatching is true)
healthMachine.PerformTransition(Activity.HaveUnprotectedSex);
Console.WriteLine($"After Unprotected Sex: {healthMachine.CurrentState}");
// Give birth to return to Reproductive
healthMachine.PerformTransition(Activity.GiveBirth);
Console.WriteLine($"After Giving Birth: {healthMachine.CurrentState}");
// Undergo surgery to become NonReproductive
healthMachine.PerformTransition(Activity.UndergoSurgery);
Console.WriteLine($"After Surgery: {healthMachine.CurrentState}");
}
}
Explanation:
-
Transitions:
-
ReachPuberty: Moves the state from
NonReproductive
toReproductive
. -
HaveUnprotectedSex: Moves the state to
Pregnant
ifParentsNotWatching
is true. -
GiveBirth: Moves the state back to
Reproductive
fromPregnant
. -
UndergoSurgery: Moves the state to
NonReproductive
, making further reproduction impossible.
-
ReachPuberty: Moves the state from
Step 5: Using Conditions and Actions
Stateless allows for advanced customization of transitions using conditions and actions.
-
Conditional Transitions:
- The
PermitIf
method enables state changes only when specific conditions are met. - In our example,
HaveUnprotectedSex
only transitions toPregnant
ifParentsNotWatching
is true.
- The
-
Actions on Entry and Exit:
- Stateless allows executing actions upon entering or exiting a state.
- We could modify the configuration to log when a transition occurs, send notifications, or perform other actions.
private void ConfigureStateMachine()
{
_stateMachine.Configure(HealthState.NonReproductive)
.Permit(Activity.ReachPuberty, HealthState.Reproductive);
_stateMachine.Configure(HealthState.Reproductive)
.OnEntry(() => Console.WriteLine("Entered Reproductive state."))
.OnExit(() => Console.WriteLine("Exiting Reproductive state."))
.PermitIf(Activity.HaveUnprotectedSex, HealthState.Pregnant, () => ParentsNotWatching)
.Permit(Activity.UndergoSurgery, HealthState.NonReproductive);
_stateMachine.Configure(HealthState.Pregnant)
.OnEntry(() => Console.WriteLine("Entered Pregnant state."))
.Permit(Activity.GiveBirth, HealthState.Reproductive)
.Permit(Activity.HaveAbortion, HealthState.Reproductive);
}
Explanation:
- OnEntry: Executes an action when entering a state.
- OnExit: Executes an action when exiting a state.
Summary of the Code Flow
-
Initialization: Sets up the state machine with
HealthState.NonReproductive
as the initial state. -
State Configurations: Defines each state and its possible transitions, using
Permit
andPermitIf
to control allowed transitions and conditions. -
Conditional Property: Uses
ParentsNotWatching
to control whether an activity leads to a transition. - OnEntry/OnExit: Demonstrates how to add actions when entering or exiting states, providing flexibility for additional logic.
Example Output
Running the code might produce output like:
Initial State: NonReproductive
After Puberty: Reproductive
After Unprotected Sex: Pregnant
After Giving Birth: Reproductive
After Surgery: NonReproductive
This output indicates how the individual transitions through different states, with each action having a specific outcome based on the current state and conditions.
Benefits of Using Stateless for State Machines
- Declarative Syntax: Stateless allows for easy definition of states, triggers, and transitions.
- Flexible Configuration: Supports conditional transitions and complex workflows.
- Hierarchical State Management: Ideal for handling complex scenarios in larger applications.
Conclusion
The Stateless library in C# is an excellent tool for managing state machines in a declarative and structured way. In this example, we demonstrated how to use Stateless for a scenario in reproductive health,
leveraging its features to define states, triggers, and conditional transitions.
For more complex state management needs, Stateless provides further customization options, including hierarchical state machines, allowing you to create robust workflows efficiently. Whether you’re handling application states, user workflows, or other finite state systems, Stateless simplifies the process, making your code more maintainable and easier to understand.
Additional Resources:
- Stateless Documentation: Stateless GitHub
- Further Reading: Check out other state management tools and libraries if you need a more specialized solution.
Top comments (0)