The state design pattern is really similar to the strategy design pattern we looked at in the previous chapter. Its purpose is to:
Basically, the difference between the state design pattern and the strategy design pattern comes from the following two points:
Implementing the state design pattern also closely resembles the implementation of the strategy design pattern.
Imagine a media player. Most media players have a play button—when we activate it, it usually changes its appearance and becomes a pause button. Clicking the pause button now also does something different—it pauses the playback and reverts to a play button. This is a good candidate for the state design pattern, where depending on which state the player is in, a different action happens.
The following class diagram shows the classes that are needed to implement this functionality for the play and pause buttons:
Our Playing
and Paused
implementations set the state to the opposite one and make our player functional. Using the state design pattern also makes our code much more elegant—we could, of course, use if
statements and depending on the value, perform different actions. However, it could easily get out of control when there are many states.
Let's have a look at the code for the class diagram that we showed previously. First of all, let's see the State
trait:
trait State[T] { def press(context: T) }
It is really simple and allows the extending classes to implement the press
method. We have two implementations according to our class diagram:
class Playing extends State[MediaPlayer] { override def press(context: MediaPlayer): Unit = { System.out.println("Pressing pause.") context.setState(new Paused) } } class Paused extends State[MediaPlayer] { override def press(context: MediaPlayer): Unit = { System.out.println("Pressing play.") context.setState(new Playing) } }
We have made them simple and they only print a relevant message and then change the current state to the opposite state.
Our model defines a MediaPlayer
class, which looks like the following:
case class MediaPlayer() { private var state: State[MediaPlayer] = new Paused def pressPlayOrPauseButton(): Unit = { state.press(this) } def setState(state: State[MediaPlayer]): Unit = { this.state = state } }
This really is everything we need. Now we can use our media player in the following application:
object MediaPlayerExample { def main(args: Array[String]): Unit = { val player = MediaPlayer() player.pressPlayOrPauseButton() player.pressPlayOrPauseButton() player.pressPlayOrPauseButton() player.pressPlayOrPauseButton() } }
If we run the preceding code, we will see the following output:
As you can see in the example output, the state changes on every button press and it performs a different action, which we've illustrated using a different print message.
A possible improvement to our application would involve making the state objects singletons. As you can see, they are always the same, so there really is no need to create new ones every single time.
52.15.245.1