一、状态模式定义
当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。(Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.)
状态模式的核心是封装,状态的变更引起了行为的变更,从外部看起来就好像这个对象对应的类发生了改变一样。
又是一个拗口的定义。。使用通俗的话来说,就是一个对象在不同的状态下,会展现出不同的样子,也就是会展现出不同的行为。
现实世界中有很多这种例子:
1、QQ登录状态不同,右下角的QQ显示的样子不一样
2、智能手机在锁屏状态下按任何按键都新要求先解锁手机;在解锁状态下则可以正常操作手机;如果没有电了,则可能会要求先充电
3、烧水壶在烧开的状态下,会冒出大量白气,发出嗡嗡的声音,甚至会顶开壶盖;但是在其它状态下却并非这样。
状态模式和有限状态机的概念紧密相关。
其主要思想是程序在任意时刻仅可处于几种有限的状态中。 在任何一个特定状态中, 程序的行为都不相同, 且可瞬间从一个状态切换到另一个状态。 不过, 根据当前状态, 程序可能会切换到另外一种状态, 也可能会保持当前状态不变。 这些数量有限且预先定义的状态切换规则被称为转移。
举个例子,用户发表文章
在不同的状态下,通过特定的操作,文章状态将会转变成其它状态。
状态模式的通用类图如下
- 上下文 (Context) 保存了对于一个具体状态对象的引用, 并会将所有与该状态相关的工作委派给它。 上下文通过状态接口与状态对象交互, 且会提供一个设置器用于传递新的状态对象。
- 状态 (State) 接口会声明特定于状态的方法。 这些方法应能被其他所有具体状态所理解, 因为你不希望某些状态所拥有的方法永远不会被调用。
- 具体状态 (Concrete States) 会自行实现特定于状态的方法。 为了避免多个状态中包含相似代码, 你可以提供一个封装有部分通用行为的中间抽象类。状态对象可存储对于上下文对象的反向引用。 状态可以通过该引用从上下文处获取所需信息, 并且能触发状态转移。
上下文和具体状态都可以设置上下文的下个状态, 并可通过替换连接到上下文的状态对象来完成实际的状态转换。
二、案例
还是以文章发布为例,想必下面的代码很多人都写过
class Document is
field state: string
// ……
method publish() is
switch (state)
"draft":
state = "moderation"
break
"moderation":
if (currentUser.role == "admin")
state = "published"
break
"published":
// 什么也不做。
break
// ……
同样的发布文章动作,用户点击发布,状态会变成审核中;管理员点击发布,状态会变成已发布。这里状态较少,所以还比较好梳理,如果有十几种状态,它们之间的关系会变的异常复杂,全放在这一坨,很快就复杂的难以维护---状态模式正是为了解决这个问题。
接下来使用状态模式重构上面的功能,类图如下
首先,定义一个表示文章状态的抽象类ArticleState,该类定义了三种文章操作的方法并引用了文章对象的实例。
public abstract class ArticleState {
protected Article article;
public ArticleState(Article article) {
this.article = article;
}
/**
* 保存草稿
*/
abstract void draft();
/**
* 发表文章
* @param user
*/
abstract void publish(User user);
/**
* 审阅文章
*/
abstract void moderation(boolean auditResult);
}
接下来,定义一个文章类Article,该类维护了其内部状态ArticleState,并定义了保存草稿、发布文章、审阅文章三种方法,并提供了文章状态转移方法changeState
public class Article {
private ArticleState articleState;
public Article() {
System.out.println("初始化文章为草稿状态");
this.articleState = new DraftState(this);
}
public void changeState(ArticleState state) {
this.articleState = state;
}
/**
* 保存草稿
*/
void draft() {
this.articleState.draft();
}
/**
* 审核文章
*/
void moderation(boolean auditResult) {
this.articleState.moderation(auditResult);
}
/**
* 发布文章
*/
void publish(User user) {
this.articleState.publish(user);
}
}
再定义一个User类,发表文章的时候会根据该身份做不同的操作。
public class User {
private Boolean isAdmin;
public User(Boolean isAdmin) {
this.isAdmin = isAdmin;
}
public Boolean isAdmin() {
return isAdmin;
}
}
最后,继承ArticleState类,实现草稿状态、审阅状态以及发布状态类
草稿状态类
public class DraftState extends ArticleState {
public DraftState(Article article) {
super(article);
}
@Override
void draft() {
System.out.println("当前为草稿状态,不做状态变更");
}
@Override
void publish(User user) {
if (user.isAdmin()) {
System.out.println("草稿状态 -> 发布状态");
this.article.changeState(new PublishedState(this.article));
} else {
this.article.changeState(new ModerationState(this.article));
System.out.println("草稿状态 -> 审核中状态");
}
}
@Override
void moderation(boolean auditResult) {
System.out.println("状态锁定");
}
}
审阅状态类
public class ModerationState extends ArticleState {
public ModerationState(Article article) {
super(article);
}
@Override
void draft() {
//状态锁定
System.out.println("状态锁定");
}
@Override
void publish(User user) {
//状态锁定
System.out.println("状态锁定");
}
@Override
void moderation(boolean auditResult) {
if (auditResult) {
//审核成功,转变成发布状态
System.out.println("审核状态 -> 发布状态");
this.article.changeState(new PublishedState(this.article));
} else {
//审核失败,转变成草稿状态
System.out.println("审核状态 -> 草稿状态");
this.article.changeState(new DraftState(this.article));
}
}
}
发布状态类
public class PublishedState extends ArticleState {
public PublishedState(Article article) {
super(article);
}
@Override
void draft() {
System.out.println("发布状态 -> 草稿状态");
this.article.changeState(new DraftState(this.article));
}
@Override
void publish(User user) {
System.out.println("当前为已发布的状态,不做状态变更");
}
@Override
void moderation(boolean auditResult) {
//状态锁定
System.out.println("状态锁定");
}
}
最后,做个测试
public class Main {
public static void main(String[] args) {
//文章初始胡为草稿状态
Article article = new Article();
User normalUser = new User(false);
//以普通用户发布文章需要审核
article.publish(normalUser);
//审核通过
article.moderation(true);
//审核通过的文章不可再次审核(状态锁定)
article.moderation(false);
//审核通过的文章如果想修改,可以保存为草稿
article.draft();
//必须先发布再审核,所以会状态锁定
article.moderation(false);
}
}
运行结果
初始化文章为草稿状态
草稿状态 -> 审核中状态
审核状态 -> 发布状态
状态锁定
发布状态 -> 草稿状态
状态锁定
在这个案例中,Article类的方法将实现都委托给了内部维护的ArticleState对象,该对象随着不同的动作,一直在发生变化,如果发生了某些不合理的变化,比如草稿状态到审阅中状态,则会打印“状态锁定”;ArticleState类内部还反向引用了Article类的实例,这是为了方便修改Article的状态。
END.
注意:本文归作者所有,未经作者允许,不得转载