📗 Object Behavioural: State
Tags: designpatterns
Published: 2021-06-21 14:56:50

## Pre-face:

I was reading about design patters here and there, but it's finally the time to systemise them a bit.

So I started reading this great book: [Design Patterns: Elements of Reusable Object-Oriented Software](https://g.co/kgs/e5mkn8).

And as usual I'm remembering better if I'm writing it down.

## TL;DR;

Use this pattern if you have lots of if-else's, your object needs to change its behaviour cause it's state changed, your object's state consists of multiple pieces which need to be checked often.

## Definition

**State** design pattern allows an object to alter its behaviour when its state changes.

Here is a formal diagram:
![UML diagram of State pattern](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/di40a0oq5406d0j7zvyp.png)

We have an object of type _Context_. It holds a reference to a state object of type _IState_. There are multiple implementations of _IState_ - _StateA_, _StateB_, etc.

Context talks to the outside world and passes calls to the _IState_ implementation it currently holds.

Concrete type of _IState_ changes when _Context_'s state changes.

## Implementation

Let's try to write some code now.

Imagine having a `Player` object, which does what players do:

- play
- stop
- forward
- rewind

```typescript
class Player {
  // Omitting constructor code

  play(): void {}
  stop(): void {}
  forward(): void {}
  rewind(): void {}
}
```

Let's start with implementing `play` and `stop` methods. When we 'push the button' we want to track time when this happened and of course let's log this into a console.

```typescript
class Player {
  // Omitting constructor code

  play(): void {
    this.time = Date.now();
    console.log(`>>>> Time: ${this.time}`);
  }
  stop(): void {
    this.time = Date.now();
    console.log(`>>>> Time: ${this.time}`);
  }

  forward(): void {}
  rewind(): void {}
}
```
Ok, we're seeing some code duplication, but let's continue for now. In real life you can't 'push the same button' twice, right? Let's add some checks.

```typescript
class Player {
  // Omitting constructor code

  play(): void {
    if (!this.isPlaying) {
      this.time = Date.now();
      this.isPlaying = true;
      this.isStopped = false;
      console.log(`>>>> Time: ${this.time}`);
    }
  }
  stop(): void {
    if (!this.isStopped) {
      this.time = Date.now();
      this.isStopped = true;
      this.isPlaying = false;
      console.log(`>>>> Time: ${this.time}`);
    }
  }

  forward(): void {}
  rewind(): void {}
}
```

Let's add `forward` and `rewind` into the mix. And let's log when 'wrong buttons are clicked'.

```typescript
class Player {
  // Omitting constructor code

  play(): void {
    if (!this.isPlaying) {
      this.time = Date.now();
    }

    if (this.isStopped) {
      console.log('>>>> [Stop]: play');
    } else if (this.isForwarding) {
      console.log('>>>> [Forward]: play');
    } else if (this.isRewinding) {
      console.log('>>>> [Rewind]: play');
    } else {
      console.log('>>>> [Play]: play');
    }

    if (!this.isPlaying) {
      this.isPlaying = true;
      this.isStopped = false;
      this.isForwarding = false;
      this.isRewinding = false;

      console.log(`>>>> Time: ${this.time}`);
    }
  }

  stop(): void {
    if (!this.isStopped) {
      this.time = Date.now();
    }

    if (this.isPlaying) {
      console.log('>>>> [Play]: stop');
    } else if (this.isForwarding) {
      console.log('>>>> [Forward]: stop');
    } else if (this.isRewinding) {
      console.log('>>>> [Rewind]: stop');
    } else {
      console.log('>>>> [Stop]: stop');
    }

    if (!this.isStopped) {
      this.isStopped = true;
      this.isPlaying = false;
      this.isForwarding = false;
      this.isRewinding = false;

      console.log(`>>>> Time: ${this.time}`);
    }
  }

  forward(): void {
    if (!this.isForwarding) {
      this.time = Date.now();
    }

    if (this.isPlaying) {
      console.log('>>>> [Play]: forward');
    } else if (this.isStopped) {
      console.log('>>>> [Stop]: forward');
    } else if (this.isRewinding) {
      console.log('>>>> [Rewind]: forward');
    } else {
      console.log('>>>> [Forward]: forward');
    }

    if (!this.isForwarding) {
      this.isForwarding = true;
      this.isStopped = false;
      this.isPlaying = false;
      this.isRewinding = false;

      console.log(`>>>> Time: ${this.time}`);
    }
  }

  rewind(): void {
    if (!this.isRewinding) {
      this.time = Date.now();
    }

    if (this.isPlaying) {
      console.log('>>>> [Play]: rewind');
    } else if (this.isStopped) {
      console.log('>>>> [Stop]: rewind');
    } else if (this.isForwarding) {
      console.log('>>>> [Forward]: rewind');
    } else {
      console.log('>>>> [Rewind]: rewind');
    }

    if (!this.isRewinding) {
      this.isRewinding = true;
      this.isStopped = false;
      this.isPlaying = false;
      this.isForwarding = false;

      console.log(`>>>> Time: ${this.time}`);
    }
  }
}
```

Well, all these if-else's are not looking good. They become longer as we add new functionality to our player and we need to keep track of all the booleans.

This is where _State_ pattern comes into picture:

```typescript
class Player extends Context<PlayerState, PlayerStateKey> {
  // Omitting some setup code

  constructor(state?: PlayerState) {
    super();
    this.state = state || new Stop(this);
  }

  play = () => this.state.play();
  stop = () => this.state.stop();
  forward = () => this.state.forward();
  backward = () => this.state.backward();
}
```

Quite concise, right?
We're hiding actual implementations into stand-alone objects (_StateA_, _StateB_, etc.) and let our _Context_ only take care of holding the reference to the _IState_.

Let's implement _Play_ state.

```typescript
class Play implements PlayerState {
  key: PlayerStateKey = 'play';

  constructor(private context: Player) {}

  play() {
    console.log('>>>> [Play]: play');
    return;
  }

  stop() {
    console.log('>>>> [Play]: stop');
    this.context.time = Date.now();
    this.context.changeState(new Stop(this.context));
    console.log(`>>>> Time: ${this.context.time}`);
  }

  forward() {
    console.log('>>>> [Play]: forward');
    this.context.time = Date.now();
    this.context.changeState(new Stop(this.context));
    console.log(`>>>> Time: ${this.context.time}`);
  }

  rewind() {
    console.log('>>>> [Play]: rewind');
    this.context.time = Date.now();
    this.context.changeState(new Stop(this.context));
    console.log(`>>>> Time: ${this.context.time}`);
  }
}
```

Every concrete _IState_ implementation needs to describe all the methods. For _Play_ we want to be logging and updating `time` for any 'button press', but play one.

We'll need to implement all other states, but they will pretty much follow the same logic in our example, so let's omit them here.

## What will it cost?

Potential Cons:

- As you can see from the full implementation [here](https://github.com/Jokerwolf/design-patterns-study/tree/main/src/state) using this pattern increases the number of files in the project. Some might consider it a bad thing.

- Adding a new function to our player (let's say _pause_) would require to add some implementation in every concrete _IState_ implementation, which might be tedious. IDEs could help with it though, but again might be considered a con for some developers.

- In current implementation concrete states (_Play_, _Stop_, etc.) are getting the context reference to work with. This might be considered an issue as we might be exposing too much.

- State transitions in our example are explicit. Concrete state knows which state to move to. This might not be ideal as we have tighter coupling and transitions might get hard to track.

Potential Pros:

- Context (_Player_ class in our example) class becomes drastically shorter and therefore easier to read and maintain.

- Adding a new state (_Pause_ for example) is as easy as adding a new class.

- Same goes for removing a state (let's we don't need that _Pause_ anymore).

- Changing a particular behaviour for the state means just changing one function (several functions in one class/file).

- No more if-else's spread across every function (for me personally this is always a huge selling point).

## Conclusion

Splitting code into multiple files is usually a good thing. I can't say that i would personally use this pattern on daily basis, but Pros are definitely overweighting Cons for me.

Hope this was a bit helpful 🙃
[Source code if needed](https://github.com/Jokerwolf/design-patterns-study)