状态模式
状态模式又称为:State。状态是一种行为设计模式,能在一个对象的内部状态变化时改变其行为,使其看上去就像改变了自身所属的类一样。
问题
有一个 文档 Document 类。 文档可能会处于 草稿(Draft) 、 审阅中 (Moderation)和 已发布(Published)三种状态中的一种。
文档的 发布(publish)方法在不同状态下的行为略有不同:
- 处于 草稿 状态时,它会将文档转移到审阅中状态。
- 处于 审阅中 状态时,如果当前用户是管理员,它会公开发布文档。
- 处于 已发布 状态时,可以撤回成 草稿 状态。
平常可能会直接将状态转换的操作写到一个方法中
代码语言:javascript复制switch ($status) {
case 'Draft':
break;
case 'Moderation':
break;
case 'Published':
break;
}
为了能根据当前状态选择完成相应行为的方法,绝大部分方法中会包含复杂的条件语句。修改其转换逻辑可能会涉及到修改所有方法中的状态条件语句,导致代码的维护工作非常艰难。这个问题会随着项目进行变得越发严重。我们很难在设计阶段预测到所有可能的状态和转换。随着时间推移,最初仅包 含有限条件语句的简洁状态机可能会变成臃肿的一团乱麻。
解决方法
我们将工作委派给一个状态对象。将所有可能状态新建一个类,然后将所有状态的对应行为抽取到这些类中。也就是说我们把具体的转化过程交个具体的状态类,而无需到使用状态的类中编写。
原始对象被称为上下文(context),它并不会自行实现所有行为,而是会保存一个指向表示当前状态的状态对象的引用,且将所有与状态相关的工作委派给该对象。
结构
State:状态基类; Draft : 实现 State 接口的具体状态类; Document:具体的使用状态模式类 ;
代码示例
状态基类
代码语言:javascript复制abstract class State
{
/**
* 当前类
* @var Document
*/
public Document $document;
public function __construct(Document $document)
{
$this->document = $document;
}
/**
* 渲染
* @return mixed
* @author chendashengpc
*/
abstract public function render();
/**
* 发布
* @return mixed
* @author chendashengpc
*/
abstract public function publish();
}
具体状态类
草稿
代码语言:javascript复制/**
* 草稿
*/
class Draft extends State
{
public function render()
{
return '草稿状态';
}
/**
* 发布为审核中
* @return mixed|void
* @author chendashengpc
*/
public function publish()
{
$this->document->changeState(new Moderation($this->document));
}
}
审查中
代码语言:javascript复制/**
* 审查中
*/
class Moderation extends State
{
public function render()
{
return '审查状态';
}
/**
* 发布为审核中
* @return mixed|void
* @author chendashengpc
*/
public function publish()
{
$this->document->changeState(new Published($this->document));
}
}
已发表
代码语言:javascript复制/**
* 已发表
*/
class Published extends State
{
public function render()
{
return '已发表状态';
}
/**
* 发布成功了,再次可变为草稿
* @return mixed|void
* @author chendashengpc
*/
public function publish()
{
$this->document->changeState(new Draft($this->document));
}
}
用状态模式类
代码语言:javascript复制class Document
{
protected State $state;
/**
* 初始化默认状态
*/
public function __construct()
{
$this->state = new Draft($this);
}
/**
* 渲染
* @return void
* @author chendashengpc
*/
public function render()
{
return $this->state->render();
}
/**
* 发布
* @return void
* @author chendashengpc
*/
public function publish()
{
return $this->state->publish();
}
/**
* 修改状态
* @param State $state
* @return void
* @author chendashengpc
*/
public function changeState(State $state)
{
$this->state = $state;
}
}
客户端使用
代码语言:javascript复制$document = new Document();
// 处于草稿阶段
echo $document->render() . PHP_EOL;
// 发布为审核状态
$document->publish();
echo $document->render() . PHP_EOL;
// 发布
$document->publish();
echo $document->render() . PHP_EOL;
输出
代码语言:javascript复制草稿状态
审查状态
已发表状态
UML
优缺点
优点
- 单一职责原则。将与特定状态相关的代码放在单独的类中。
- 开闭原则。无需修改已有状态类和上下文就能引入新状态。
- 通过消除臃肿的状态机条件语句简化上下文代码。
缺点
- 如果状态机只有很少的几个状态,或者很少发生改变,那么应用该模式可能会显得小题大作。