The State Pattern provides a structured approach to partitioning the behaviour within complex systems. The overall behaviour of a system is partitioned into well-defined states. Typically, each state is implemented by a class. The overall system behaviour can be determined firstly by knowing the current state of the system; secondly, by understanding the behaviour possible while in that state (as embodied in the methods of the class corresponding to that state).
Here is an example:
Here is the output:
This example was inspired from a similar Ruby Example. One of the great things about a dynamic language like Groovy though is that we can take this example and express it in many different ways depending on our particular needs. Some potential variations for this example are shown below.
One approach we could take is to leverage Interface-Oriented Design. To do this, we could introduce the following interface:
Offline classes could be modified to implement that interface, e.g.:
You might ask: Haven't we just introduced additional boilerplate code? Can't we rely on duck-typing for this? The answer is 'yes' and 'no'. We can get away with duck-typing but one of the key intentions of the state pattern is to partition complexity. If we know that the client class and each state class all satisfy one interface, then we have placed some key boundaries around the complexity. We can look at any state class in isolation and know the bounds of behaviour possible for that state.
We don't have to use interfaces for this, but it helps express the intent of this particular style of partitioning and it helps reduce the size of our unit tests (we would have to have additional tests in place to express this intent in languages which have less support for interface-oriented design).
Alternatively, or in combination with other variations, we might decide to extract some of our State Pattern logic into helper classes. For example, we could define the following classes in a state pattern package/jar/script:
This is all quite generic and can be used wherever we want to introduce the state pattern. Here is what our code would look like now:
You can see here the
transitionTo methods begin to give our example code a DSL feel.
Alternatively, or in combination with other variations, we might decide to fully embrace a Domain Specific Language (DSL) approach to this example.
We can define the following generic helper functions (first discussed here):
Now we can define and test our state machine like this:
This example isn't an exact equivalent of the others. It doesn't use predefined
Offline classes. Instead it defines the entire state machine on the fly as needed. See the previous reference for more elaborate examples of this style.
See also: Model-based testing using ModelJUnit