Design patterns: State

Mauto 3 min

The State Pattern is a behavioral pattern that lets an object change its behavior based on its internal state. This pattern is useful when an object needs to change its behavior at runtime depending on a state that varies dynamically. Instead of using a series of conditionals to handle the various states, the pattern encapsulates each state in a separate class, allowing for a cleaner, more scalable design.

Strengths

  1. Less complex conditional code: It avoids the use of numerous if-else or switch-case statements to determine behavior based on state.
  2. Adding new states: Adding new states is simple and requires no changes to existing classes, making the code more extensible.
  3. Separation of concerns: Each state is encapsulated in its own class, clearly separating the behavior specific to each state.
  4. Easier maintenance: Changes to the behavior of a single state don’t affect other parts of the code.
  5. Better management of the state lifecycle: The pattern lets you handle transitions between states in an explicit, controlled way.

Use case: A coffee machine

Imagine a coffee machine that can be in several states: No coin, Has coin, Dispensing coffee. Depending on the state, the interaction with the machine changes.

PHP Code

// Interface for the states
interface CoffeeMachineState {
    public function insertCoin();
    public function pressButton();
    public function dispense();
}

// No Coin state
class NoCoinState implements CoffeeMachineState {
    private $machine;

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

    public function insertCoin() {
        echo "Coin inserted.\n";
        $this->machine->setState($this->machine->getHasCoinState());
    }

    public function pressButton() {
        echo "Insert a coin before pressing the button.\n";
    }

    public function dispense() {
        echo "No coffee dispensed.\n";
    }
}

// Has Coin state
class HasCoinState implements CoffeeMachineState {
    private $machine;

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

    public function insertCoin() {
        echo "Coin already inserted.\n";
    }

    public function pressButton() {
        echo "Button pressed. Dispensing in progress.\n";
        $this->machine->setState($this->machine->getDispensingState());
    }

    public function dispense() {
        echo "Press the button before getting your coffee.\n";
    }
}

// Dispensing state
class DispensingState implements CoffeeMachineState {
    private $machine;

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

    public function insertCoin() {
        echo "Please wait until dispensing is complete.\n";
    }

    public function pressButton() {
        echo "Please wait until dispensing is complete.\n";
    }

    public function dispense() {
        echo "Coffee dispensed.\n";
        $this->machine->setState($this->machine->getNoCoinState());
    }
}

// Coffee Machine class
class CoffeeMachine {
    private $noCoinState;
    private $hasCoinState;
    private $dispensingState;
    private $currentState;

    public function __construct() {
        $this->noCoinState = new NoCoinState($this);
        $this->hasCoinState = new HasCoinState($this);
        $this->dispensingState = new DispensingState($this);
        $this->currentState = $this->noCoinState;
    }

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

    public function insertCoin() {
        $this->currentState->insertCoin();
    }

    public function pressButton() {
        $this->currentState->pressButton();
    }

    public function dispense() {
        $this->currentState->dispense();
    }

    public function getNoCoinState() {
        return $this->noCoinState;
    }

    public function getHasCoinState() {
        return $this->hasCoinState;
    }

    public function getDispensingState() {
        return $this->dispensingState;
    }
}

// Usage example
$coffeeMachine = new CoffeeMachine();

// Interactions with the coffee machine
$coffeeMachine->pressButton();  // Insert a coin before pressing the button.
$coffeeMachine->insertCoin();   // Coin inserted.
$coffeeMachine->pressButton();  // Button pressed. Dispensing in progress.
$coffeeMachine->dispense();     // Coffee dispensed.
$coffeeMachine->pressButton();  // Insert a coin before pressing the button.

Explanation

  • CoffeeMachineState interface: Defines the methods that every state of the machine must implement.
  • Concrete states (NoCoinState, HasCoinState, DispensingState): Implement the interface and handle the machine’s behavior according to the state.
  • CoffeeMachine class: Holds a set of states and manages the transitions between them.
  • Using the Machine: Transitions between states happen based on the user’s interactions with the machine, with no need to explicitly handle complex conditional logic.

BACK TO THE GUIDE

Copiato