设计模式(十九):状态模式(State Pattern)

Published on 2024-04-08 11:08 in 分类: 博客 with 狂盗一枝梅
分类: 博客

一、状态模式定义

当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。(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、烧水壶在烧开的状态下,会冒出大量白气,发出嗡嗡的声音,甚至会顶开壶盖;但是在其它状态下却并非这样。

状态模式和有限状态机的概念紧密相关。

有限状态机

其主要思想是程序在任意时刻仅可处于几种有限状态中。 在任何一个特定状态中, 程序的行为都不相同, 且可瞬间从一个状态切换到另一个状态。 不过, 根据当前状态, 程序可能会切换到另外一种状态, 也可能会保持当前状态不变。 这些数量有限且预先定义的状态切换规则被称为转移

举个例子,用户发表文章

problem2-zh

在不同的状态下,通过特定的操作,文章状态将会转变成其它状态。

状态模式的通用类图如下

状态模式通用类图
  1. 上下文 (Context) 保存了对于一个具体状态对象的引用, 并会将所有与该状态相关的工作委派给它。 上下文通过状态接口与状态对象交互, 且会提供一个设置器用于传递新的状态对象。
  2. 状态 (State) 接口会声明特定于状态的方法。 这些方法应能被其他所有具体状态所理解, 因为你不希望某些状态所拥有的方法永远不会被调用。
  3. 具体状态 (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
    // ……

同样的发布文章动作,用户点击发布,状态会变成审核中;管理员点击发布,状态会变成已发布。这里状态较少,所以还比较好梳理,如果有十几种状态,它们之间的关系会变的异常复杂,全放在这一坨,很快就复杂的难以维护---状态模式正是为了解决这个问题。

接下来使用状态模式重构上面的功能,类图如下

状态模式-第 3 页.drawio

首先,定义一个表示文章状态的抽象类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.


#设计模式
目录