Basics of the State Design Pattern
The State Design Pattern can be used to model the state of objects. For this purpose, the
state-dependent parts of a class called Context are swapped out into individual
so-called State classes.
As can be seen in the diagram below, all state classes implement a common state interface.
Figure 1: State classes and state interface
The context has a reference to this interface. This is the current state of the context.
It also implements the state interface and forwards all calls to its current state.
To enable state changes, the context is given a setter for the current state and is
injected into the concrete state implementations via constructor. Each state thus
determines its subsequent states by calling the setter on the context.
The design pattern replaces decision structures such as if-else or
switch-case in the context class with polymorphic structures. This makes the
individual classes smaller, easier to understand, and easier to test. Furthermore, the
system can easily be extended to include more states without having to adapt the context
itself.
Application example in Android
Since Android 6.0, certain permissions (hereafter referred to as permissions) are only
requested from the user during use. The app should therefore be functional regardless of
whether the permission has been granted or not.
One of these special permissions, which is considered dangerous, is access to the current
location of the device via GPS. So if you want to implement a search that shows you e.g.
all parking spaces in the vicinity, you have to ask the user for the permission to
access his location.
So depending on whether the user grants the permission or not, the search must be
executed. The simplest approach for this is to define a boolean flag in the LocationSearch
class, which is set depending on the user selection.
However, it has to be noted that the user additionally has the option to disable the GPS
in the settings of his Android device. To map this, the LocationSearch class
would therefore need another flag.
This implementation causes other code to be executed in many places in the LocationSearch
class depending on the two flags. This ensures that the LocationSearch class
becomes relatively large and confusing. Maintainability is low, the code is difficult to
adapt to new requirements, and achieving high test coverage for this class is relatively
complicated and costly.
But how can the State Design Pattern be used to avoid this? First, all conceivable states
must be captured. For this purpose, GPS and permission states are considered separately,
as shown in the following graphic.
Figure 2: GPS and permission state separated
In order to apply the pattern, the two separate states must now be combined. This results
in the following states:
- Permission granted - GPS On
- Permission granted - GPS Off
- Permission not granted - GPS On
- Permission not granted - GPS Off
Now we will examine which state changes (called transactions in the following) are
possible. These are shown graphically below.
Figure 3: GPS and permission state combined
The diagram shows the individual states and the possible transactions. In this example it
is possible to switch the GPS on and off. Furthermore, it is possible to grant or revoke
the permission to use the GPS. These four transactions are included as methods in our
state interface.
Transactions that do not require an action for a Special State are simply implemented
empty. So the state PermissionGrantedGPSoff implements the methods off and permissionGranted
for example empty.
The logic that depends on the state can now be moved to the corresponding state classes.
This creates smaller classes that are easier to keep track of and test. The complete
class diagram for example can be seen in Figure 4.
Figure 4: Diagram of all classes