状态模式
本课属于 Coddy PHP 学习旅程的“面向对象编程”单元。第 79 课,共 91 课。
状态模式是一种行为型设计模式,它允许对象在内部状态发生变化时改变行为。你无需使用复杂的条件语句来处理不同状态,而是将每种状态封装在自己的类中,并将行为委托给当前状态对象。
想一想自动售货机。它会根据当前状态采取不同的行为:等待投币、收到付款,或者正在出货。
每种状态都有自己的规则,用来规定允许执行哪些操作。状态模式通过为每种状态创建一个类来实现这一点。
<?php
interface OrderState {
public function proceed(Order $order): string;
public function getStatus(): string;
}
class PendingState implements OrderState {
public function proceed(Order $order): string {
$order->setState(new ProcessingState());
return "Order moved to processing";
}
public function getStatus(): string {
return "Pending";
}
}
class ProcessingState implements OrderState {
public function proceed(Order $order): string {
$order->setState(new ShippedState());
return "Order has been shipped";
}
public function getStatus(): string {
return "Processing";
}
}
class ShippedState implements OrderState {
public function proceed(Order $order): string {
return "Order already shipped";
}
public function getStatus(): string {
return "Shipped";
}
}
上下文类持有对当前状态的引用,并将调用委托给它:
<?php
class Order {
private OrderState $state;
public function __construct() {
$this->state = new PendingState();
}
public function setState(OrderState $state): void {
$this->state = $state;
}
public function proceed(): string {
return $this->state->proceed($this);
}
public function getStatus(): string {
return $this->state->getStatus();
}
}
$order = new Order();
echo $order->getStatus() . "\n";
echo $order->proceed() . "\n";
echo $order->getStatus() . "\n";
echo $order->proceed() . "\n";
echo $order->getStatus();
输出:
Pending
Order moved to processing
Processing
Order has been shipped
Shipped状态模式可以消除冗长的 switch 语句,让添加新状态变得简单。每个状态类都负责知道下一个状态是什么,从而让逻辑更有条理,也更易于维护。
挑战
简单让我们使用 State Pattern 构建一个文档工作流系统。内容管理系统中的文档会经历不同阶段:草稿、审核和已发布。每个阶段都有自己的规则,规定允许执行哪些操作,以及尝试推进文档时会发生什么。
你将把代码组织在四个文件中:
DocumentState.php:定义一个DocumentState接口,为所有文档状态规定契约。它应包含两个方法:publish(Document $document): string用于尝试推进文档,getStatus(): string用于返回当前状态名称。States.php:引入 DocumentState 接口,并创建三个实现该接口的状态类:DraftState:调用publish()时,将文档切换到ReviewState,并返回"Document sent for review"。它的状态是"Draft"。ReviewState:调用publish()时,将文档切换到PublishedState,并返回"Document published"。它的状态是"Under Review"。PublishedState:调用publish()时,返回"Document is already published",但不改变状态。它的状态是"Published"。
Document.php:引入 States 文件,并创建一个作为上下文的Document类。你的文档应当:- 在构造函数中接收标题,并以
DraftState作为初始状态 - 包含一个用于更改当前状态的
setState(DocumentState $state): void方法 - 包含一个
publish(): string方法,将调用委托给当前状态的publish()方法 - 包含一个
getStatus(): string方法,返回当前状态的状态信息
- 在构造函数中接收标题,并以
main.php:引入 Document 文件。你会收到一个输入:在文档上调用publish()的次数。创建一个标题为
"My Article"的Document。首先,打印初始状态。然后,按指定次数调用publish(),并将每次调用的结果打印在新的一行。最后,打印最终状态。输出格式:
Status: [initial status] [publish result 1] [publish result 2] ... Status: [final status]
每个状态类都知道工作流中的下一个状态,因此转换逻辑清晰有序,也便于修改。添加一个像 "Archived" 这样的新状态,只需创建一个新类并更新相关转换即可。
亲手试试
<?php
require_once 'Document.php';
// 读取输入,即调用 publish() 的次数
$n = intval(trim(fgets(STDIN)));
// TODO:创建一个标题为 "My Article" 的 Document 对象
// TODO:按 "Status: [status]" 格式打印初始状态
// TODO:调用 publish() n 次,每次的结果打印在新的一行
// TODO:按 "Status: [status]" 格式打印最终状态
?>这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: PHP 在线编译器