1670412026, 09:58 Stale Machines in Practice: Implementing Solutions for Real Challenges -DEV Community
State Machines in Practice: Implementing Solutions for Real Challenges
¥computerscience#programming#pythonitcodenewbie
If you've studied engineering, you've probably heard about State Machines. But beyond the theory,
you might not have seen how they're used. Did you know that State Machines play a role in many
everyday applications? In this article, we'll refresh our understanding of State Machines and explore
their practical applications. Plus, we'll learn how to implement them quickly using Python. Ready to
dive in? Let's go!
‘State’ is a common programming term that is experienced by all developers as they advance from
beginning to intermediate-level programming. So, what exactly does the term "State" mean?
In general, an object's state is merely the current snapshot of the object or a portion of it. Meanwhile,
in computer science, a program's state is defined as its position about previously stored inputs. In this
context, the term "state" is used in the same manner as it is in science: the state of an object, such as a
gas, liquid, or solid, represents its current physical nature, and the state of a computer program reflects
its current values or contents.
The stored inputs are preserved as variables or constants in a computer program. While assessing the
state of a program, developers might examine the values contained in these inputs. The state of the
program may change while it runs - variables may change, and memory values may change. A control
variable, such as one used in a loop, for example, changes the state of the program at each iteration.
Examining the present state of a program may be used to test or analyze the codebase.
In simpler systems, state management is frequently handled with if-else, if-then-else, try-catch
statements, or boolean flags; however, this is useless when there are too many states imaginable in a
program, They can lead to clunky, complicated code that is difficult to understand, maintain, and
debug.
One disadvantage of if-else-clauses or booleans is that they may get fairly extensive, and adding
another state is difficult because it necessitates rewriting the code of many different classes.
Let's build a video player for example:
class Video:
def _init_(self, source):
self source = source
seltis_playing = False
self.is_paused = False
self.is_stopped = True
# A video can only be played when paused or stopped
def play(self):
if not self.is_playing or self.is_paused:
# Make the call to play the video
self.is_playing = True
self.is_paused = False
else:
raise Exception(
‘Cannot play a video that is already playing.’
)
# A video can only be paused when it is playing
nips: dev tolpcagativerma 8state-machines-r-practce-Implementng-solution-forseal-challenges-3176 sno‘1042024, 09:56 Siale Machines in Pracce: Implementing Soluion for Real Challenges - DEV Community
def pause(selt)
if selfis_playing:
# Make the call to pause the video
self.is_playing = False
selfis_paused = True
else:
raise Exception(
‘Cannot pause a video that is not playing’
)
# A video can only be stopped when it is playing or paused
def stop(self)
if self.is_playing or selfis_paused
# Make the call to stop the video
seltis_playing = False
self.is_paused = False
else:
raise Exception(
‘Cannot stop a video that is not playing or paused!
)
The above code snippet is an if-else implementation of a simple video player application, where the
three basic states are - playing, paused, and stopped. However, if we try to add more states, the code
will rapidly become complex, bloated, repetitive, and hard to understand and test. Let's see what the
code looks like when adding another state ‘rewind’:
class Video:
def _init_(self, source):
self source = source
self.is_playing = False
self.is_paused = False
self.is_rewinding = False
self.is_stopped = True
# A video can only be played when it is paused or stopped or rewinding
def play(self):
if selfis_paused or setfis_stopped or selt.is_rewinding:
# Make the call to play the video
selfis_playing = True
self.is_paused = False
self.is_stopped = False
self.is_rewinding = False
else:
raise Exception(
‘Cannot play a video that is already playing.’
)
nitps: dev tolpcagativerma 8state-machines-r-practce-Implementng-solution-forseal-hallenges-3176 2110‘1042024, 09:56 Siale Machines in Pracce: Implementing Soluion for Real Challenges - DEV Community
# A video can only be paused when it is playing or rewinding
def pause(self)
if self.is_playing or selfis_rewinding:
# Make the call to pause the video
selfis_playing = False
selfis_paused = True
self.is_rewinding = False
seltis_stopped = False
else:
raise Exception(
‘Cannot pause a video that is not playing or rewinding’
)
# A video can only be stopped when it is playing or paused or rewinding
def stop(self)
if selfis_playing or self'is_paused or self.is_rewinding
# Make the call to stop the video
self.is_playing = False
selfis_paused = False
self.is_stopped = True
self.is_rewinding = False
else:
raise Exception(
‘Cannot stop a video that is not playing or paused or rewinding’
)
# 4, A video can only be rewinded when it is playing or paused
def rewind\sel):
if self.is_playing or selfis_paused:
# Make the call to rewind the video
selfis_playing = False
selfis_paused = False
selfis_stopped = False
self.is_rewinding = True
else:
raise Exception(
“Cannot rewind a video that is not playing or paused’
)
Without the state pattern, you'd have to examine the program's current state throughout the code,
including the update and draw methods. If you want to add a fourth state, such as a settings screen,
you'll have to update the code of many distinct classes, which is inconvenient. This is where the idea of
state machines comes in handy.
What is a state machine?
State machines are not a novel concept in computer science; they are one of the basic design patterns
utilized in the software business. It is more system-oriented than coding-oriented and is used to
nips: dev tolpcagativerma 8state-machines-r-practce-Implementng-solution-forseal-challenges-3176 30‘1042024, 09:56 Siale Machines in Pracce: Implementing Soluion for Real Challenges - DEV Community
model around use cases.
Let's look at a simple real-life example of hiring a cab through Uber
When you initially launch the program, it takes you to the home screen, where you type in your
destination in the search area.
Once the right location has been identified, Uber displays recommended travel options such as Pool,
Premier, UberGo, Uber XL, and others, along with a pricing estimate.
The trip is confirmed and a driver is assigned once you select the payment option and press the
‘Confirm’ button with the specified journey time if necessary.
Uber now displays a map on which you can locate your driver.
Screen 1 is the first screen that all users in this use case see, and itis self-contained. Screen 2 is reliant
on Screen 1, and you will not be able to go to Screen 2 until you give accurate data on Screen 1.
Likewise, screen 3 is dependent on screen 2, while screen 4 is dependent on screen 3. If neither you
nor your driver cancels your trip, you'll be moved to screen 4, where you won't be able to plan another
trip until your current one ends.
Let's say it's raining severely and no driver accepts your trip or no available driver in your region is
found to finish your travel; an error notification warning you of driver unavailability shows, and you
remain on screen 3. You may still return to screen 2, screen 1, and even the very first screen.
You are in a different step of the cab reservation process, and you may only go to the next level if a
specified action in the current stage is successful. For example, if you input the wrong location on
screen 1, you won't be able to proceed to screen 2, and you won't be able to proceed to screen 3
unless you pick a travel option on screen 2, but you may always return to the previous stage unless
your trip is already booked.
In the above example, we've divided the cab booking process into several activities, each of which may
or may not be authorized to call another activity based on the status of the booking. A state machine
is used to model this. In principle, each of these stages/states should be autonomous, with one
summoning the next only after the current one has been finished, successfully or otherwise.
In more technical words, the state machine enables us to split down a large complicated action into a
succession of separate smaller activities, such as the cab booking activity in the preceding example.
Events connect smaller tasks, and shifting from one state to another is referred to as transition. We
normally conduct some actions after changing from one state to another, such as creating a booking
in the back end, issuing an invoice, saving user analytics data, capturing booking data in a database,
triggering payment after the trip is finished, and so on.
Hence, the general formula for a state machine can be given as:
Current State + Some Action / Event= Another State
Let's see what a state machine designed for a simple video player application would look like:
nitps: dev tolpcagativerma 8state-machines-r-practce-Implementng-solution-forseal-hallenges-3176 401670412026, 09:58 Stale Machines in Practice: Implementing Solutions for Real Challenges - DEV Community
Player
And we can implement it in code using transitions as follows
from transitions import Machine
class Video:
# Define the states
PLAYING = ‘playing’
PAUSED = ‘paused’
STOPPED = ‘stopped’
def _init_(self, source):
self.source = source
# Define the transitions
transitions = [
#1. A video can only be played when it is paused or stopped.
{‘trigger’: ‘play’, 'source': self PAUSED, ‘dest’: self. PLAYING),
{‘trigger’: ‘play’, ‘source’: self. STOPPED, ‘dest’: self PLAYING),
# 2. A video can only be paused when it is playing
(‘trigger ‘pause’, 'source': self. PLAYING, ‘dest’: self.PAUSED),
# 3. A video can only be stopped when it is playing or paused,
{trigger 'stop’, ‘source’: self PLAYING, ‘dest’: self STOPPED),
(trigger: ‘stop’, ‘source’: self, PAUSED, ‘dest’: self STOPPED),
nitps: dev tolpcagativerma 8state-machines-r-practce-Implementng-solution-forseal-hallenges-3176 5101670412026, 09:58 Stale Machines in Practice: Implementing Solutions for Real Challenges -DEV Community
# Create the state machine
self.machine = Machine{
model = self,
transitions = transitions,
initial = self STOPPED
}
def play(self):
pass
def pause(selt)
pass
def stop(self)
pass
Now, in case, we want to add another state, say rewind, we can do that easily as follows:
from transitions import Machine
class Video:
hitps:iidevofpragatverma 8isate-machines in-practice-mplementing-slutions-for-ea-challenges-3176 6101670412026, 09:58 Stale Machines in Practice: Implementing Solutions for Real Challenges -DEV Community
# Define the states
PLAYING = ‘playing’
PAUSED = ‘paused!
STOPPED = ‘stopped’
REWINDING = ‘rewinding’ # new
def _init_(self, source):
self.source = source
# Define the transitions
transitions = [
# 1. A video can only be played when it is paused or stopped.
{trigger’: ‘play’, ‘source’: self PAUSED, ‘dest’: self PLAYING),
{'trigger’: ‘play’, ‘source’: self STOPPED, ‘dest’: self PLAYING),
trigger’: ‘play, ‘source’: self REWINDING, ‘dest’: self PLAYING), # new
# 2. Avideo can only be paused when it is playing.
trigger’: ‘pause’, ‘source’: self PLAYING, ‘dest’: self PAUSED),
{'trigger’: ‘pause’, 'source': self REWINDING, ‘dest’: self PAUSED}, # new
# 3. A video can only be stopped when it is playing or paused.
{trigger’: ‘stop’, ‘source’: self. PLAYING, ‘dest’: self STOPPED),
{trigger’: ‘stop’, ‘source’: self. PAUSED, ‘dest’: self STOPPED),
{trigger’: ‘stop’, ‘source’: self REWINDING, ‘dest’: self STOPPED}, # new
# 4, A video can only be rewinded when it is playing or paused.
{'trigger’: ‘rewind’, ‘source’: self PLAYING, ‘dest’: self REWINDING), #new
{ trigger’: ‘rewind’, ‘source’: self. PAUSED, ‘dest’: self. REWINDING), # new
]
# Create the state machine
self.machine = Machine{
model = self,
transitions = transitions,
initial = self.STOPPED
}
def play(self):
pass
def pause(selt)
pass
def stop(self)
pass
def rewind(selt)
pass
nips: dev tolpcagativerma 8state-machines-r-practce-Implementng-solution-forseal-challenges-3176
701670412026, 09:58 Stale Machines in Practice: Implementing Solutions for Real Challenges -DEV Community
Thus, we can see how state machines can simplify a complex implementation and save us from writing
incorrect code. Having leamed the capabilities of state machines, it's now important to understand
why and when to use state machines.
Why & When to Use State Machines?
State Machines can be utilized in applications that have distinct states. Each stage can lead to one or
more subsequent states, as well as end the process flow. A State Machine employs user input or in-
state computations to choose which state to enter next.
Many applications necessitate an “initialize” stage, followed by a default state that allows for a wide
range of actions, Previous and present inputs, as well as states, can all have an impact on the actions
that are executed. Clean-up measures can then be carried out when the system is "shut down.”
Astate machine can help us conceptualize and manage those units more abstractly if we can break
down a hugely complex job into smaller, independent units, where we simply need to describe when a
state can transition to another state and what happens when the transition occurs. We don't need to
be concerned with how the transition occurs after setup. After that, we only need to think about when
and what, not how.
Furthermore, state machines let us see the entire state process in a very predictable way; once
transitions are set, we don't have to worry about mismanagement or erroneous state transitions; the
improper transition may occur only if the state machine is configured properly. We have a
comprehensive view of all states and transitions in a state machine.
if we don't use a state machine, we are either unable to visualize our systems in various possible states,
or we are knowingly or unknowingly coupling our components tightly together, or we are writing
many if-else conditions to simulate state transitions, which complicates unit and integration testing
because we must ensure that all test cases are written to validate the possibility of all the conditions
and branching used
Advantages of State Machines
State machines, in addition to their ability to develop decision-making algorithms, are functional forms
of application planning. As applications get more complex, the need for effective design grows.
State diagrams and flowcharts are useful and occasionally essential throughout the design process.
State Machines are important not just for application planning but are also simple to create.
Following are some of the major advantages of state machines in modern-day computing:
It helps you eliminate hard coding conditions. On your behalf, the state machine abstracts all logic
related to states and transitions.
State machines often have a finite number of states with definite transitions, making it simple to
identify which transition/data/event triggered the present state of a request.
After establishing a state machine, developers may concentrate on building actions and preconditions.
With sufficient validation and preconditioning, state machines restrict out-of-order operations. As in
the Uber example, a driver cannot be rewarded until the voyage is done.
State machines can be quite easy to maintain. The actions taken during each transition are logically
independent of one another. As a result, the corresponding code can be isolated.
State machines are less prone to change and are more stable. It becomes much easier to maintain
such systems if the current and future use cases are very obvious.
Disadvantages of State Machines
Not everything about state machines is good, they can sometimes lead to drawbacks and challenges
too. Here are some of the common problems with state machines:
nips: dev tolpcagativerma 8state-machines-r-practce-Implementng-solution-forseal-challenges-3176 ano1670412026, 09:58 Stale Machines in Practice: Implementing Solutions for Real Challenges -DEV Community
State machines are usually synchronous. So, if you need asynchronous background API calls/job
execution, you'll have to weigh the pros and cons carefully before deciding on the best option.
The code can quickly get jumbled. Because state machines are data-driven, your product team may ask
you to execute different transitions from the same state based on different data/input parameters. As
a result, this type of demand may result in multiple transitions with a clumsy precondition check. It is
entirely dependent on the product and the machine's current configuration,
If you need to load balance state machine instances, go with the one that has persistence enabled;
otherwise, you'll need to implement your persistence layer and necessary validations to ensure that
multiple requests fired at separate state machine instances produce consistent results.
Because there are few resources or communities dedicated to distinct state machine implementations,
assistance may be limited once you've chosen a library.
Things to keep in mind when using State Machines
When using a state machine, your system should ideally have two logical components:
the state machine/workflow system itself
the business logic contained in one or more services.
The state machine may be thought of as the infrastructure that drives state transitions; it verifies state
transitions and executes configured actions before, during, and after a transition; however, it should
not know what business logic is done in those actions.
So, in general, it's a good idea to isolate the state machine from the core business logic by using
correct abstractions; otherwise, managing the code would be a nightmare
Here are some other real-life scenarios where we need to employ state machine logic with caution:
A state machine is more than just states, transitions, and actions. It should also be capable of defining
a border around a state change. A transition can only be successful in particular cases if it is triggered
by a trustworthy system or user. There might be a variety of similar situations. As a consequence, we
should be able to develop appropriate state transition guarding logic.
We commonly end up with many processes for the same business entity that can run concurrently. In
such cases, one process does not obstruct other workflows; they may or may not be triggered
concurrently, but they may coexist; the second workflow may begin from one of the first workflow's
eligible phases, after which it may branch off and work independently. This sort of use case is
established by the business; not every organization will have it.
In principle, workflow systems are independent of the business domain. As a consequence, in the same
workflow system, many processes with no link to the same business organization can be established.
They may have a shared or distinct starting point, depending on whether the workflow system enables
multiple starting points.
When numerous separate workflows are formed in the same workflow system, you get a global picture
of all business processes running across various business entities in your system. Depending on the
business use cases, different processes may also have certain identical stages.
Practical or real-life use cases for state machines:
Following are some of the practical applications that benefit from the concept of state machines in our
daily lives:
Single-page or tabbed dialogue boxes A tab in the conversation box represents each state. A user can
initiate a state transition by selecting a certain tab. The status for each tab includes any actions that the
user can take.
A self-service banking machine (ATM). In this application, states such as waiting for user input,
confirming the needed amount against the account balance, distributing the money, printing the
nitps: dev tolpcagativerma 8state-machines-r-practce-Implementng-solution-forseal-hallenges-3176 et0‘1042024, 09:56 Siale Machines in Pracce: Implementing Soluion for Real Challenges - DEV Community
receipt, and so on are all conceivable.
A software that takes a single measurement, stores it in memory, and then waits for the user to take
another action. This program's steps include waiting for user input, performing the measurement,
recording the data, displaying the results, and so on, Configuring ETL Jobs, for example.
State machines are commonly used for designing user interfaces. While designing a user interface,
distinct user actions shift the user interface into separate processing segments. Each of these elements
will function as a state in the State Machine. These segments can either lead to another segment for
further processing or wait for another user event. In this scenario, the State Machine monitors the user
to determine what action they should take next
When you purchase something from an online e-commerce site, for example, it goes through various
phases, such as Ordered, Packed, Shipped, Cancelled, Delivered, Paid, Refunded, and so on. The
transition occurs automatically as things move through a warehouse or logistics center and are
scanned at various stages, such as when a user cancels or wants a refund.
Process testing is another typical application for State Machines. In this example, each stage of the
process is represented by a state. Depending on the results of each state's exam, a separate state may
be declared. This might happen frequently if the process under examination is thoroughly studied.
Conclusion
The notion of state machines is extremely useful in programming. It not only streamlines the process
of developing more complicated use case apps but also reduces the development work necessary. It
provides a more simple and elegant grasp of modern-day events and when correctly applied, may
work miracles.
If you find this insightful, do let me know your views in the comments. Also, any kind of feedback is
welcome, In case you want to connect with me, follow the links below:
nips: dev tolpcagativerma 8state-machines-r-practce-Implementng-solution-forseal-challenges-3176 10110