djsharman / state
状态设计模式。用PHP实现,风格独特。
Requires
- php: >=5.3.0
- djsharman/libraries: *
- phpunit/php-token-stream: *
- phpunit/phpunit: 5.0.*
- sebastian/finder-facade: *
- theseer/fdomdocument: *
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标记中。
通过测试文档化状态机
使用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
这份自动生成的清单清楚地说明了状态机三种状态之间允许的转换。