djsharman/state

该包最新版本(v1.0)没有提供许可证信息。

状态设计模式。用PHP实现,风格独特。

v1.0 2015-10-30 13:42 UTC

This package is auto-updated.

Last update: 2024-09-29 04:48:38 UTC


README

这是一个基于Sebastian Bergmann工作的状态机生成器(感谢Sebastian)。

状态设计模式的目的是“允许对象在其内部状态改变时改变其行为。对象将看起来改变了其类”。状态模式可用于有效地实现有限状态机,例如,在实现业务流程或工作流时非常有用。

本版本与原始版本的不同之处如下

  • 您可以在任何时候重新生成您的状态机而不会丢失您的代码(下面说明如何操作)。

  • 现在,您可以使用不同的.xml文件同时生成多个状态机。您可以在配置文件中配置输出目录。

  • 它优先考虑约定而非配置,因此已删除一些配置文件选项。

  • 生成的类现在是命名空间化的,因此您可以使用您的PSR自动加载器。

  • 它生成的状态类有两个额外的onEnterState和onExitState方法。这些方法在状态机进入或离开状态时自动调用。但我猜您自己已经明白了;-)

  • 状态机将主状态机实例的指针传递给每个状态,当状态实例化时。这意味着状态可以修改状态机的状态。$this->SM->{操作函数名}(); 例如,从门示例中的 $this->SM->close()。

  • 可视化器现在基于状态机的配置文件生成输出,而不是尝试从代码中生成。

  • 可视化器现在使用状态改变操作名称标记状态转换箭头。

  • 生成的测试可能不完整,我还没有测试它们。

  • 示例已更新 - 在示例文件夹中,您可以找到Sebastian使用新代码生成的门示例。还有一个使用此代码创建的更大的真实世界状态机示例,用于处理电话呼叫。真实代码已被移除,但状态机的主要结构在这里供您查看。您可以看到当状态机变大时生成的图示有多疯狂。

确保您的代码不会被覆盖

当您修改生成器生成的函数之一时,请确保将代码移动到

//###START_CUSTOMCODE2

//###END_CUSTOMCODE2

注释之间。例如,请参阅PwrCall示例文件夹中的StartUpState.php文件中的onEnterState (源代码) 函数。

将生成器实现与现有代码一起使用

您需要创建一个或多个XML文件来表示您的状态机。下面将详细介绍完整的XML状态机配置。重要的是您应该设置目标目录 targetdir,其中状态机将被生成。这是一个相对于您运行生成器的地方的路径。此外,您应该设置状态机类将使用的命名空间

 <configuration>
  <targetdir name="statemachines"/>
  <namespace name="djsharman\examples\statemachines"/>
 </configuration>

然后您应该创建一个shell脚本(或批处理文件)来调用生成器,并且可选地调用可视化器。

php "..\generator\run.php" _defs
php "..\generator\runViz.php" _defs

生成器和可视化器接受一个参数,即包含状态机XML定义的目录。

示例:门

考虑一个代表门的类Door。门可以处于三种状态之一:打开、关闭、锁定。当Door对象从其他对象接收消息(如open()close()lock()unlock())时,它会根据当前状态做出不同的响应。例如,open()消息的效果取决于门是否处于关闭状态(例如,锁着的门必须先解锁才能打开)。状态模式描述了Door对象如何在每种状态下表现出不同的行为。该模式的关键思想是引入表示门状态的类。

DoorState

DoorState接口([源代码](https://github.com/djsharman/state/blob/HEAD/examples/statemachines/Door/DoorState.php))声明了一个接口,该接口适用于表示不同状态的所有类。

<?php
interface DoorState
{
    public function open();
    public function close();
    public function lock();
    public function unlock();
}

AbstractDoorState

AbstractDoorState类([源代码](https://github.com/djsharman/state/blob/HEAD/examples/statemachines/Door/AbstractDoorState.php))以这种方式实现了DoorState接口所需的操作,即默认情况下所有方法都会引发IllegalStateTransitionException

<?php
abstract class AbstractDoorState implements DoorState
{
    public function open()
    {
        throw new IllegalStateTransitionException;
    }

    public function close()
    {
        throw new IllegalStateTransitionException;
    }

    public function lock()
    {
        throw new IllegalStateTransitionException;
    }

    public function unlock()
    {
        throw new IllegalStateTransitionException;
    }
}

OpenDoorState, ClosedDoorState, 和 LockedDoorState

OpenDoorState([源代码](https://github.com/djsharman/state/blob/HEAD/examples/statemachines/Door/OpenDoorState.php))、ClosedDoorState([源代码](https://github.com/djsharman/state/blob/HEAD/examples/statemachines/Door/ClosedDoorState.php))和LockedDoorState([源代码](https://github.com/djsharman/state/blob/HEAD/examples/statemachines/Door/LockedDoorState.php))是AbstractDoorState的子类,它们适当地覆盖了open()close()lock()unlock()方法,以返回表示新状态的对象。例如,OpenDoorState::close()返回一个ClosedDoorState的实例。

<?php
class OpenDoorState extends AbstractDoorState
{
    public function close()
    {
        return new ClosedDoorState;
    }
}

Door

Door类([源代码](https://github.com/djsharman/state/blob/HEAD/examples/statemachines/Door/Door.php))维护一个表示门当前状态的类(AbstractDoorState子类的一个实例)。

<?php
class Door
{
    private $state;

    public function __construct(DoorState $state)
    {
        $this->setState($state);
    }

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

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

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

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

    private function setState(DoorState $state)
    {
        $this->state = $state;
    }
}

Door类将所有特定于状态的消息转发到这个状态对象。每当门改变状态时,Door对象都会更改它所使用的状态对象。

使用示例

<?php
require __DIR__ . '/src/autoload.php';

$door = new Door(new OpenDoorState);
var_dump($door->isOpen());

$door->close();
var_dump($door->isClosed());

$door->lock();
var_dump($door->isLocked());

$door->lock();

上面的示例脚本产生以下输出

bool(true)
bool(true)
bool(true)

Fatal error: Uncaught exception 'IllegalStateTransitionException' in AbstractDoorState.php:25
Stack trace:
#0 Door.php(35): AbstractDoorState->lock()
#1 example.php(13): Door->lock()
#2 {main}
  thrown in AbstractDoorState.php on line 25

生成状态机

使用代码生成器,可以将上述代码自动从如下所示的XML规范生成

<?xml version="1.0" encoding="UTF-8"?>
<specification>
 <configuration>
  <targetdir name="statemachines"/>
  <namespace name="djsharman\examples\statemachines"/>
 </configuration>
 <states>
  <state name="OpenDoorState"/>
  <state name="ClosedDoorState"/>
  <state name="LockedDoorState"/>
 </states>
 <transitions>
  <transition from="ClosedDoorState" to="OpenDoorState"   operation="open"/>
  <transition from="OpenDoorState"   to="ClosedDoorState" operation="close"/>
  <transition from="ClosedDoorState" to="LockedDoorState" operation="lock"/>
  <transition from="LockedDoorState" to="ClosedDoorState" operation="unlock"/>
 </transitions>
</specification>

根据约定,假设状态机配置的部分内容。

  • 状态机的名称取自XML配置文件的名称
  • 状态测试始终以给定状态名称加前缀"is"开始。例如,对OpenDoorState的测试将是isOpenDoorState();
  • 测试中的操作名称始终以"testCan"或"testCannot"为前缀。例如,对于OpenDoorTest,将是不允许打开(testCannotOpen)和允许关闭(testCanClose)。

可视化状态机

可以基于状态机的XML规范生成状态机状态及其转换的图形可视化。

runViz.php([源代码](https://github.com/djsharman/state/blob/HEAD/generator/runViz.php))将状态机的表示作为有向图生成在Dot标记中。

Visualization of the Door state machine

通过测试文档化状态机

使用PHPUnit的TestDox功能,我们可以根据状态机的测试自动生成状态机的文档。

OpenDoor
 [x] Can be closed
 [x] Cannot be opened
 [x] Cannot be locked
 [x] Cannot be unlocked

ClosedDoor
 [x] Cannot be closed
 [x] Can be opened
 [x] Can be locked
 [x] Cannot be unlocked

LockedDoor
 [x] Cannot be closed
 [x] Cannot be opened
 [x] Cannot be locked
 [x] Can be unlocked

这份自动生成的清单清楚地说明了状态机三种状态之间允许的转换。