Design patterns: State
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
- Less complex conditional code: It avoids the use of numerous
if-elseorswitch-casestatements to determine behavior based on state. - Adding new states: Adding new states is simple and requires no changes to existing classes, making the code more extensible.
- Separation of concerns: Each state is encapsulated in its own class, clearly separating the behavior specific to each state.
- Easier maintenance: Changes to the behavior of a single state don’t affect other parts of the code.
- 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.