The state pattern

In the state pattern, a class behavior changes based on its state. This type of design pattern comes under the behavior pattern.

In the state pattern, we create objects that represent various states and a context object whose behavior varies as its state object changes.

Role

The role of this pattern is to adapt its behavior depending on the internal state of an object. It can be used when implementing the dependency of the state object if the condition statement becomes complex.

Design

The generic class diagram structure of the state pattern is as follows:

Design

Participants

The participants in the state pattern are as follows:

  • StateMachine: This is a concrete class that describes a state machine's objects, which means that they have a set of states that can be described in a state transition diagram. This class has a reference to an instance of a sub class that implements the state abstract class and defines the current state.
  • IState: This is an abstract class that introduces you to the methods signature of a state behavior.
  • ConcreteStateA and ConcreteStateB: These are concrete subclasses that implement the behavioral methods depending on the state.

Collaboration

The StateMachine object delegates the call of the method to a ConcreteState object depending on the current state.

The StateMachine object can send a reference to itself and to the ConcreteState object if needed. This reference can then be sent through the initialization of the concreteState object or each time calls are delegated.

Illustration

Your company needs to sell a new device that has only two buttons that can play the radio or some music. You need to have the following functionalities, depending on the current state of the device:

State

Action button

Source button

Radio

This changes to the next station and plays it

This changes to music playing mode

Playing music

This pauses the music

This changes to standby mode

Pausing music

This plays the music

This changes to standby mode

Standby

This changes to radio mode

This does nothing

The preceding table shows us that we have four states to implement. Depending on the state, the button's behaviors will not be the same.

Implementation

First, open the StatePattern.xcodeproj file to see the structure of the project.

Our audio player device is represented by the Player.swift class in the ConcreteClassWithState folder.

Our common interface that defines the method signature of a state behavior is defined in the IPlayerState.swift file. Each state that implements the IPlayerState interfaces are grouped in the ConcreteState folder.

The main.swift file contains our demo client:

Implementation

As always, we will first define our interface. Each state will implement a behavior for each of the two buttons visible on our audio player and pass the device object as an argument. This will allow the current state object to manipulate the current state of the audio player object:

protocol IAudioPlayerState{
  func buttonAction(player:AudioPlayer)
  func buttonSource(player:AudioPlayer)
}

Then, we can implement our audio player. The init method is waiting to receive a concreteState instance that we will keep in mind in the state variable.

We define our two buttons. Each of them will delegate the request to the state object by invoking the appropriate button.

We then add a computed property called CurrentState that allows us to return the current state of the audio player or to change it through the state objects.

The final code of the AudioPlayer class is as follows:

import Foundation

class AudioPlayer {
  private var state:IAudioPlayerState!
  
  required init(state:IAudioPlayerState){
    self.state = state
  }
  
  //Press the Action Button
  func ActionButton(){
    state.buttonAction(self)
  }
  
  //Press the Source Button
  func SourceButton(){
    state.buttonSource(self)
  }
  
  var CurrentState:IAudioPlayerState{
    get{
      return state
    }
    set{
      state = newValue
    }
  }
}

Our player is now ready and the interface implemented by the state's objects is defined. We can now start with coding our first state: the RadioState class.

This class represents the state where the audio player plays the radio:

import Foundation

class RadioState: IAudioPlayerState {
  
  init(){
    print("RADIO MODE")
  }
  
  func buttonSource(player: AudioPlayer) {
    print("Changing to MUSIC Mode")
    player.CurrentState = MusicPlayingState()
  }
  
  func buttonAction(player: AudioPlayer) {
    print("Choosing next Station & playing it")
  }
}

The implementation is quite simple; we inform the init() method that we are in the radio mode. We implement the IAudioPlayerState protocol and the buttonSource and buttonAction methods.

As we are in the radio mode, pressing buttonAction will change it to the next station, and clicking on the source button will move it to the MusicPlaying state.

To change the state of the audio player, we only need to make a call to the CurrentState property of the player object:

    player.CurrentState = MusicPlayingState()

Using the same logical implementation and based on the table in the preceding example, we can complete our code. The following code is the implementation of the MusicPlayingState class:

class MusicPlayingState: IAudioPlayerState {
  
  init(){
    print("MUSIC PLAY MODE")
  }
  
  func buttonSource(player: AudioPlayer) {
    print("Changing source to Standby Mode")
    player.CurrentState = StandByState()
  }
  
  func buttonAction(player: AudioPlayer) {
    print("Changing to Pausing Mode")
    player.CurrentState = MusicPausedState()
  }
}

The following code is the implementation of the MusicPausedState class:

class MusicPausedState: IAudioPlayerState {
  
  init(){
    print("MUSIC PAUSED MODE")
  }
  
  func buttonSource(player: AudioPlayer) {
    print("Changing source to Standby Mode")
    player.CurrentState = StandByState()
  }

  func buttonAction(player: AudioPlayer) {
    print("Changing to playing Mode")
    player.CurrentState = MusicPlayingState()
  }
}

The following code is the implementation of the StandBySTate class:

class StandByState: IAudioPlayerState {
  
  init(){
    print("STANDBY MODE")
  }
  
  func buttonSource(player: AudioPlayer) {
    print("Changing to Radio Mode")
    player.CurrentState = RadioState()
  }
  
  func buttonAction(player: AudioPlayer) {
    print("cannot launch an action in standby mode")
  }
}

Our player is now ready to work. We will code our demo case to test whether the functionalities implemented are working as described in the table given in the introduction of the sample.

Open the main.swift file and write the following code:

let standbyMode = StandByState()
let player = AudioPlayer(state: standbyMode)

player.ActionButton()
player.SourceButton()

player.ActionButton()
player.SourceButton()

player.ActionButton()
player.ActionButton()
player.SourceButton()

First, we instantiate the first state where our audio player will be. We decide to put it in the StandBy mode.

Then, we instantiate our audio player and pass the standbymode state as an argument. Finally, we will simulate an action on by clicking the action or source button. Let's run the code, and you will see the result, as shown in the following example:

Implementation

We start in the standby mode. The action button tells us that we cannot use it in the standby mode. So, we click on the source button and enter in the radioMode. We push the action button again; this changes to the next station and plays it.

We push the source button again and change to the music mode by playing the music. We push the action button and the music is paused. We then push the action button again and the music is played again.

Finally, we push the source button and the audio player comes back in the audio mode.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset