State Design Pattern

State Design Pattern

Design patterns 101

ยท

6 min read

The Story

Hey everyone. I am back with another design pattern. If you are new to the blog and willing to learn design patterns, you can find the explained design patterns in this series.

Have you heard about Banana Man? Of course not. That is a character in my world, The Banana Republic. Unlike other characters, this Banana Man sometimes acts as a Super Hero and as well as a Super Villain. Also, he travels among the people as just a normal person while hiding his true identity from the world (Of course I know who he is. But I won't tell you ๐Ÿค).

Did you ever think about how he manages to change his identity or the STATES? No, because you are so selfish, you only think about yourself. JK!

Let's see how this Banana Man manages to change the states according to the work he has been assigned to.

Let's look at the below code.

class BananaMan
{
    public function __construct($state)
    {
        if ($state == 'hero') {
            // Do hero stuff.
        } else if ($state == 'villain') {
            // Do villain stuff.
        } else if ($state == 'human') {
            // Blend with normal people.
        } else {
            // I do not know. I just put else because I thought it would be nice.
        }
    }
}

// Ha! I see some villains.
$bananaMan = new BananaMan('hero');

// Heroes are taking over the world. I should be a super villain.
$bananaMan = new BananaMan('villain');

// I am tired. I will just walk on the street and blend.
$bananaMan = new BananaMan('human');

See how easily he changes his identity. Was this hard? NOPE! So, what were you afraid of?

Hmm! I do not know. Maybe he has to change himself all over again if there was another state of himself. Like a prince maybe?

EXACTLY! If he gets another state (as you mentioned, a prince state. A prince superhero? Good thought for a Disney movie) he has to change his core-self (what I meant by that is to change the base class. Sigh!). So, it would be hard to maintain. How do we make this more maintainable?

Base class changes can lead to multiple issues like broken logic that was implemented previously.

Let's see how we can manage the situation with a design pattern.

For starters, I'll create an interface for Banana Man's states.

interface BananaState
{
    public function doYourThing();
}

Now let's create Banana Man's different states as classes.

class SuperHero implements BananaState
{
    public function doYourThing()
    {
        // Do hero stuff.
    }
}

class SuperVillain implements BananaState
{
    public function doYourThing()
    {
        // Do villain stuff.
    }
}

class Human implements BananaState
{
    public function doYourThing()
    {
        // Blend with normal people.
    }
}

What I have simply done here is, create separate classes for the states by implementing interface BananaState . After implementing interface BananaState , I have to override the function doYourThing() because it has been defined in the interface. Then inside doYourThing() , I would implement the logic that needs to be done in these different states.

Now, to the base class.

class BananaMan
{
    private $state;

    public function __construct(BananaState $state)
    {
        $this->state = $state;
    }

    public function setState(BananaState $state)
    {
        $this->state = $state;
    }

    public function changeState()
    {
        $this->state->doYourThing();
    }
}

Here, I have created the BananaMan class, and there I created a constructor with a parameter of interface BananaState . Once, you pass your state to the constructor, it will get assigned to the global $state variable and you can use the variable inside the base class to change the state. setState() is to change the state without creating an object again. changeState() is to trigger the state change that was assigned through the constructor.

By doing this, whenever you add a state, the base class remains unchanged. So, you can add or remove statuses as you wish without touching the base class.

// I am walking as a normal person.
$bananaMan = new BananaMan(new Human);
$bananaMan->changeState();

// Hm! That guy stole the purse of that innocent lady. Time to be a Super Hero.
$bananaMan->setState(new SuperHero);
$bananaMan->changeState();

// I am just bored. It is time to play with the cops.
$bananaMan->setState(new SuperVillain);
$bananaMan->changeState();

See how easily he changes his identity for his different states? Here, if he wanted to be a prince, he can be without touching his core (the base class). The only thing you need to do is create a new class for his new identity and implements it with the interface, and call changeState().

This design pattern is called the State Design Pattern. Because it allows you to change the state of an object without touching the base class. This is highly maintainable and very easy to use.

Usage of this design pattern

  • Handling state transitions: The state design pattern may be used to handle an object's state transitions, such as switching from one state to another based on particular criteria.

  • Simplifying complex code: By enclosing state-specific activity in distinct classes, the state design pattern can assist to simplify complex code, making it easier to understand and maintain.

  • Changing behaviour at runtime: The state design pattern enables an object to alter its behaviour at runtime by swapping out its state object instead than modifying the object itself.

  • Decoupling behaviour from implementation: The state design pattern separates the implementation of distinct states into independent classes, making it easier to alter or add new states without impacting existing behaviour.

Real-life examples of the pattern

  • Traffic lights: A real-world example of the state design pattern is traffic lights. A traffic light can be in one of three states: red, yellow, or green. The traffic light's behaviour (i.e., the colour it shows) is determined by its internal status.

  • Elevator: Another example of the state design pattern is an elevator. An elevator can be in three states: going up, travelling down, and idle. The present state of the elevator determines its behaviour (e.g., the level it stops on, whether it travels up or down).

  • Automated teller machine (ATM): A nice illustration of the state design pattern is an ATM. An ATM can be in several states, including idle, processing a transaction, and distributing cash. The present state of the ATM determines its behaviour (for example, which options it offers to the user).

  • Music player: A music player can be in numerous states, including playing, pausing, and stopping. The current state of the music player determines its behaviour (e.g., whether it plays or pauses music).

    See how you have already used this concept without knowing?

    What can you see in a design pattern like this?

  • Context: The context is the object that maintains a reference to the current state object and delegates state-specific behaviour to the state object.

  • State: The state is an interface that defines the behaviour that each state must implement.

  • Concrete states: Concrete states are classes that implement the behaviour defined in the state interface. Each concrete state represents a different state of the context.

  • Client: The client is the code that uses the context and state objects.

That is it for the State Design Pattern. Will come to you with another design pattern very soon.

Till then, then.

ย