Alternatives to State Design Pattern

387 Views Asked by At

So I have been reading some design patterns on refactoring.guru, and have especially had an interest in behavioral design patterns.

However, there is one design pattern that made me question if it is taking the Single-Responsibility Principle too far: the State Pattern.

Also the fact that the states are supposed to be dependent on each make me wonder if taking them out in separate classes is a good idea after all.

Let's take the example given on their website itself: the Audio Player. Here is the pseudocode:

// The AudioPlayer class acts as a context. It also maintains a
// reference to an instance of one of the state classes that
// represents the current state of the audio player.
class AudioPlayer is
    field state: State
    field UI, volume, playlist, currentSong

    constructor AudioPlayer() is
        this.state = new ReadyState(this)

        // Context delegates handling user input to a state
        // object. Naturally, the outcome depends on what state
        // is currently active, since each state can handle the
        // input differently.
        UI = new UserInterface()
        UI.lockButton.onClick(this.clickLock)
        UI.playButton.onClick(this.clickPlay)
        UI.nextButton.onClick(this.clickNext)
        UI.prevButton.onClick(this.clickPrevious)

    // Other objects must be able to switch the audio player's
    // active state.
    method changeState(state: State) is
        this.state = state

    // UI methods delegate execution to the active state.
    method clickLock() is
        state.clickLock()
    method clickPlay() is
        state.clickPlay()
    method clickNext() is
        state.clickNext()
    method clickPrevious() is
        state.clickPrevious()

    // A state may call some service methods on the context.
    method startPlayback() is
        // ...
    method stopPlayback() is
        // ...
    method nextSong() is
        // ...
    method previousSong() is
        // ...
    method fastForward(time) is
        // ...
    method rewind(time) is
        // ...


// The base state class declares methods that all concrete
// states should implement and also provides a backreference to
// the context object associated with the state. States can use
// the backreference to transition the context to another state.
abstract class State is
    protected field player: AudioPlayer

    // Context passes itself through the state constructor. This
    // may help a state fetch some useful context data if it's
    // needed.
    constructor State(player) is
        this.player = player

    abstract method clickLock()
    abstract method clickPlay()
    abstract method clickNext()
    abstract method clickPrevious()


// Concrete states implement various behaviors associated with a
// state of the context.
class LockedState extends State is

    // When you unlock a locked player, it may assume one of two
    // states.
    method clickLock() is
        if (player.playing)
            player.changeState(new PlayingState(player))
        else
            player.changeState(new ReadyState(player))

    method clickPlay() is
        // Locked, so do nothing.

    method clickNext() is
        // Locked, so do nothing.

    method clickPrevious() is
        // Locked, so do nothing.


// They can also trigger state transitions in the context.
class ReadyState extends State is
    method clickLock() is
        player.changeState(new LockedState(player))

    method clickPlay() is
        player.startPlayback()
        player.changeState(new PlayingState(player))

    method clickNext() is
        player.nextSong()

    method clickPrevious() is
        player.previousSong()


class PlayingState extends State is
    method clickLock() is
        player.changeState(new LockedState(player))

    method clickPlay() is
        player.stopPlayback()
        player.changeState(new ReadyState(player))

    method clickNext() is
        if (event.doubleclick)
            player.nextSong()
        else
            player.fastForward(5)

    method clickPrevious() is
        if (event.doubleclick)
            player.previous()
        else
            player.rewind(5)

The states in the code are dependent on each other. It kind-of feels like broken encapsulation, and while it does seem to be a better solution than placing all the methods in AudioPlayer itself, I still feel like there would be better ways to design the whole Audio-Player project itself.

When I compare this to the Strategy design pattern, where all the strategies are unaware of each other, I find state pattern to be quite wanting, like it doesn't help much with the readability. I know the use-cases are quite different, but the Strategy pattern makes me wonder if there is a better way to set up the Audio-Player class such that we could use something other than State pattern.

So my question is, what would be the best direct alternatives to State design pattern? And also, what would be some of the best ways to set up the Audio-Player class such that it can make use of other design patterns?

1

There are 1 best solutions below

0
StepUp On

The states in the code are dependent on each other. It kind-of feels like broken encapsulation, and while it does seem to be a better solution than placing all the methods in AudioPlayer itself

I believe if we put all methods in one class, then it is necessary to use multiple if else statements to define what the button should do when it is clicked. It seems that it becomes a complete nightmare to support these multiple if else statements, but if we encapsulate a behaviur in a class, then we should edit just one class and it complies with open/closed principle. So, in my opinion, State design pattern is the appropriate design pattern used for the current case because

  • it encapsulates behaviours in classes. So if it is necessary to edit a behavior, then you will edit in a class without diving in if else statements and a fear that if else statement can be incorrectly evaluated
  • it complies with open / closed principle
  • it reduces count of if else statements