Skip to content

状态模式(State)

前言

状态模式允许对象在内部状态改变时改变其行为。它将状态逻辑从对象中分离,避免大量条件语句,是状态机、工作流、游戏开发的经典模式。本文将详细讲解状态模式的核心原理及实际应用。


一、核心概念

1.1 定义

允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

1.2 核心角色

角色说明
上下文(Context)持有当前状态,委托状态处理
状态接口(State)定义状态行为接口
具体状态(ConcreteState)实现特定状态的行为

1.3 与策略模式的区别

维度策略模式状态模式
用途算法切换状态切换
切换方式客户端决定状态自动切换
关系策略间独立状态间有转换关系
生命周期一次性使用持续转换

二、代码实现

2.1 基础实现

php
<?php
// 状态接口
interface State
{
    public function handle(Context $context): void;
}

// 上下文
class Context
{
    private State $state;

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

    public function setState(State $state): void
    {
        $this->state = $state;
    }

    public function request(): void
    {
        $this->state->handle($this);
    }
}

// 具体状态 A
class ConcreteStateA implements State
{
    public function handle(Context $context): void
    {
        echo "State A: handling request" . PHP_EOL;
        $context->setState(new ConcreteStateB());
    }
}

// 具体状态 B
class ConcreteStateB implements State
{
    public function handle(Context $context): void
    {
        echo "State B: handling request" . PHP_EOL;
        $context->setState(new ConcreteStateA());
    }
}

// 使用
$context = new Context(new ConcreteStateA());
$context->request(); // State A → B
$context->request(); // State B → A
$context->request(); // State A → B

2.2 实际案例:订单状态机

php
<?php
// 订单状态接口
interface OrderState
{
    public function pay(Order $order): void;
    public function ship(Order $order): void;
    public function deliver(Order $order): void;
    public function cancel(Order $order): void;
}

// 上下文:订单
class Order
{
    private OrderState $state;
    public string $id;

    public function __construct(string $id)
    {
        $this->id = $id;
        $this->state = new PendingState();
    }

    public function setState(OrderState $state): void
    {
        $this->state = $state;
    }

    public function pay(): void
    {
        $this->state->pay($this);
    }

    public function ship(): void
    {
        $this->state->ship($this);
    }

    public function deliver(): void
    {
        $this->state->deliver($this);
    }

    public function cancel(): void
    {
        $this->state->cancel($this);
    }
}

// 待支付状态
class PendingState implements OrderState
{
    public function pay(Order $order): void
    {
        echo "Order {$order->id}: Payment received" . PHP_EOL;
        $order->setState(new PaidState());
    }

    public function ship(Order $order): void
    {
        echo "Cannot ship: order not paid" . PHP_EOL;
    }

    public function deliver(Order $order): void
    {
        echo "Cannot deliver: order not paid" . PHP_EOL;
    }

    public function cancel(Order $order): void
    {
        echo "Order {$order->id}: Cancelled" . PHP_EOL;
        $order->setState(new CancelledState());
    }
}

// 已支付状态
class PaidState implements OrderState
{
    public function pay(Order $order): void
    {
        echo "Already paid" . PHP_EOL;
    }

    public function ship(Order $order): void
    {
        echo "Order {$order->id}: Shipped" . PHP_EOL;
        $order->setState(new ShippedState());
    }

    public function deliver(Order $order): void
    {
        echo "Cannot deliver: not shipped" . PHP_EOL;
    }

    public function cancel(Order $order): void
    {
        echo "Order {$order->id}: Refunded and cancelled" . PHP_EOL;
        $order->setState(new CancelledState());
    }
}

// 已发货状态
class ShippedState implements OrderState
{
    public function pay(Order $order): void
    {
        echo "Already paid" . PHP_EOL;
    }

    public function ship(Order $order): void
    {
        echo "Already shipped" . PHP_EOL;
    }

    public function deliver(Order $order): void
    {
        echo "Order {$order->id}: Delivered" . PHP_EOL;
        $order->setState(new DeliveredState());
    }

    public function cancel(Order $order): void
    {
        echo "Cannot cancel: already shipped" . PHP_EOL;
    }
}

// 已签收状态
class DeliveredState implements OrderState
{
    public function pay(Order $order): void
    {
        echo "Already paid" . PHP_EOL;
    }

    public function ship(Order $order): void
    {
        echo "Already shipped" . PHP_EOL;
    }

    public function deliver(Order $order): void
    {
        echo "Already delivered" . PHP_EOL;
    }

    public function cancel(Order $order): void
    {
        echo "Cannot cancel: already delivered" . PHP_EOL;
    }
}

// 已取消状态
class CancelledState implements OrderState
{
    public function pay(Order $order): void
    {
        echo "Cannot pay: order cancelled" . PHP_EOL;
    }

    public function ship(Order $order): void
    {
        echo "Cannot ship: order cancelled" . PHP_EOL;
    }

    public function deliver(Order $order): void
    {
        echo "Cannot deliver: order cancelled" . PHP_EOL;
    }

    public function cancel(Order $order): void
    {
        echo "Already cancelled" . PHP_EOL;
    }
}

// 使用
$order = new Order("ORD-001");
$order->ship(); // Cannot ship: order not paid
$order->pay(); // Payment received
$order->ship(); // Shipped
$order->cancel(); // Cannot cancel: already shipped
$order->deliver(); // Delivered

2.3 实际案例:红绿灯

php
<?php
// 交通灯状态
interface TrafficLightState
{
    public function next(TrafficLight $light): void;
    public function getColor(): string;
    public function getDuration(): int;
}

class RedLight implements TrafficLightState
{
    public function getColor(): string
    {
        return "RED";
    }

    public function getDuration(): int
    {
        return 30;
    }

    public function next(TrafficLight $light): void
    {
        echo "RED → GREEN" . PHP_EOL;
        $light->setState(new GreenLight());
    }
}

class GreenLight implements TrafficLightState
{
    public function getColor(): string
    {
        return "GREEN";
    }

    public function getDuration(): int
    {
        return 25;
    }

    public function next(TrafficLight $light): void
    {
        echo "GREEN → YELLOW" . PHP_EOL;
        $light->setState(new YellowLight());
    }
}

class YellowLight implements TrafficLightState
{
    public function getColor(): string
    {
        return "YELLOW";
    }

    public function getDuration(): int
    {
        return 5;
    }

    public function next(TrafficLight $light): void
    {
        echo "YELLOW → RED" . PHP_EOL;
        $light->setState(new RedLight());
    }
}

class TrafficLight
{
    private TrafficLightState $state;

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

    public function setState(TrafficLightState $state): void
    {
        $this->state = $state;
    }

    public function change(): void
    {
        echo "Current: {$this->state->getColor()} ({$this->state->getDuration()}s)" . PHP_EOL;
        $this->state->next($this);
    }
}

// 使用
$light = new TrafficLight();
$light->change(); // RED → GREEN
$light->change(); // GREEN → YELLOW
$light->change(); // YELLOW → RED

三、适用场景

场景说明
状态机订单、工单、审批流
游戏角色状态站立、跑步、跳跃、攻击
自动售货机投币、选择商品、出货
文档审批草稿、审核中、已发布
网络连接连接中、已连接、断开

四、优缺点分析

优点缺点
消除条件语句类数量增加
状态切换清晰状态多时复杂
符合开闭原则状态间耦合
行为与状态绑定可能导致状态爆炸
易于扩展新状态简单状态过度设计

五、常见踩坑与问题排查

5.1 状态间循环依赖

php
<?php
// 问题:A 依赖 B,B 依赖 A,可能导致循环
class StateA implements State
{
    public function handle(Context $context): void
    {
        $context->setState(new StateB()); // A → B
    }
}
class StateB implements State
{
    public function handle(Context $context): void
    {
        $context->setState(new StateA()); // B → A
    }
}

// 解决:明确状态转换规则,必要时使用状态转换表

5.2 状态爆炸

php
<?php
// 问题:状态太多,难以维护
// 解决:使用状态转换表或状态机库

// 状态转换表(PHP 原生实现)
$transitions = [
    'pending' => ['pay' => 'paid', 'cancel' => 'cancelled'],
    'paid' => ['ship' => 'shipped', 'cancel' => 'cancelled'],
    'shipped' => ['deliver' => 'delivered'],
    'delivered' => [],
    'cancelled' => [],
];

// 简单状态机
class OrderMachine
{
    private string $current = 'pending';

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

    public function trigger(string $event): void
    {
        $next = $this->transitions[$this->current][$event] ?? null;
        if ($next !== null) {
            echo "{$this->current} → {$next}" . PHP_EOL;
            $this->current = $next;
        } else {
            echo "Cannot {$event} in state {$this->current}" . PHP_EOL;
        }
    }
}

六、优化方案与进阶

6.1 状态转换表

php
<?php
// 使用表驱动代替状态类
class TableStateMachine
{
    private string $current = 'pending';

    /** @var array<string, array<string, string>> */
    private array $transitions = [
        'pending' => ['pay' => 'paid', 'cancel' => 'cancelled'],
        'paid' => ['ship' => 'shipped', 'cancel' => 'cancelled'],
        'shipped' => ['deliver' => 'delivered'],
        'delivered' => [],
        'cancelled' => [],
    ];

    public function trigger(string $event): void
    {
        $next = $this->transitions[$this->current][$event] ?? null;
        if ($next !== null) {
            echo "{$this->current} → {$next}" . PHP_EOL;
            $this->current = $next;
        } else {
            echo "Cannot {$event} in state {$this->current}" . PHP_EOL;
        }
    }
}

6.2 共享状态对象

php
<?php
// 单例状态:避免频繁创建状态对象
final class States
{
    private function __construct() {}

    public static PendingState $pending;
    public static PaidState $paid;
    public static ShippedState $shipped;
    public static DeliveredState $delivered;
    public static CancelledState $cancelled;

    public static function init(): void
    {
        self::$pending = new PendingState();
        self::$paid = new PaidState();
        self::$shipped = new ShippedState();
        self::$delivered = new DeliveredState();
        self::$cancelled = new CancelledState();
    }
}

// 初始化
States::init();

class Order
{
    private OrderState $state = States::$pending;
}

七、全文总结

状态模式的核心是 将状态逻辑封装为独立对象,状态切换时改变行为

核心要点

  1. 上下文持有当前状态,委托状态处理请求
  2. 每个状态独立类,避免条件语句
  3. 适用于状态机、工作流、游戏等场景
  4. 与策略模式相似,但状态模式关注状态转换
  5. 可用状态转换表、状态机库简化复杂状态