baby sword‘s blog baby sword‘s blog
首页
  • java基础
  • java进阶
大数据
  • mysql

    • mysql索引
    • mysql日志
  • redis

    • 单机下的redis
    • 集群下的redis
  • Spring
  • springboot
  • RPC
  • netty
  • mybatis
  • maven
  • 消息队列
  • kafka
  • zookeeper
  • rocketmq
  • 七大设计原则
  • 创建型模式
  • 结构型模式
  • 行为型模式
  • SpringCloud

    • eureka
  • SpringCloud Alibaba

    • nacos
  • 计算机网络
  • 操作系统
  • 算法
  • 个人项目
  • 个人面试面经
  • 八股记忆
  • 工作积累
  • 逻辑题
  • 面试

    • 百度后端实习二面
GitHub (opens new window)

zhengjian

不敢承担失去的风险,是不可能抓住梦想的
首页
  • java基础
  • java进阶
大数据
  • mysql

    • mysql索引
    • mysql日志
  • redis

    • 单机下的redis
    • 集群下的redis
  • Spring
  • springboot
  • RPC
  • netty
  • mybatis
  • maven
  • 消息队列
  • kafka
  • zookeeper
  • rocketmq
  • 七大设计原则
  • 创建型模式
  • 结构型模式
  • 行为型模式
  • SpringCloud

    • eureka
  • SpringCloud Alibaba

    • nacos
  • 计算机网络
  • 操作系统
  • 算法
  • 个人项目
  • 个人面试面经
  • 八股记忆
  • 工作积累
  • 逻辑题
  • 面试

    • 百度后端实习二面
GitHub (opens new window)
  • 设计模式七大原则
  • 创建型模式

  • 结构型模式

  • 行为型模式

    • 模板方法模式
    • 观察者模式
    • 迭代器模式
    • 策略模式
    • 状态模式
      • 介绍
      • 解决方案
        • 与策略模式的区别
      • 结构
      • 代码案例
      • 优缺点
  • UML

  • 设计模式
  • 行为型模式
xugaoyi
2023-01-01
目录

状态模式

状态模式是一种行为设计模式, 让你能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。

# 介绍

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

有限状态机

有限状态机。

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

你还可将该方法应用在对象上。 假如你有一个 文档Document类。 文档可能会处于 草稿Draft 、 审阅中Moderation和 已发布Published三种状态中的一种。 文档的 publish发布方法在不同状态下的行为略有不同:

  • 处于 草稿状态时, 它会将文档转移到审阅中状态。
  • 处于 审阅中状态时, 如果当前用户是管理员, 它会公开发布文档。
  • 处于 已发布状态时, 它不会进行任何操作。

文档对象的全部状态

文档对象的全部状态和转移。

状态机通常由众多条件运算符 ( if或 switch ) 实现, 可根据对象的当前状态选择相应的行为。 “状态” 通常只是对象中的一组成员变量值。 即使你之前从未听说过有限状态机, 你也很可能已经实现过状态模式。 下面的代码应该能帮助你回忆起来。

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
    // ……
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

当我们逐步在 文档类中添加更多状态和依赖于状态的行为后, 基于条件语句的状态机就会暴露其最大的弱点。 为了能根据当前状态选择完成相应行为的方法, 绝大部分方法中会包含复杂的条件语句。 修改其转换逻辑可能会涉及到修改所有方法中的状态条件语句, 导致代码的维护工作非常艰难。

这个问题会随着项目进行变得越发严重。 我们很难在设计阶段预测到所有可能的状态和转换。 随着时间推移, 最初仅包含有限条件语句的简洁状态机可能会变成臃肿的一团乱麻。

# 解决方案

状态模式建议为对象的所有可能状态新建一个类, 然后将所有状态的对应行为抽取到这些类中。

原始对象被称为上下文 (context), 它并不会自行实现所有行为, 而是会保存一个指向表示当前状态的状态对象的引用, 且将所有与状态相关的工作委派给该对象。

文档将工作委派给一个状态对象

文档将工作委派给一个状态对象。

如需将上下文转换为另外一种状态, 则需将当前活动的状态对象替换为另外一个代表新状态的对象。 采用这种方式是有前提的: 所有状态类都必须遵循同样的接口, 而且上下文必须仅通过接口与这些对象进行交互。

# 与策略模式的区别

这个结构可能看上去与策略 (opens new window)模式相似, 但有一个关键性的不同——在状态模式中, 特定状态知道其他所有状态的存在, 且能触发从一个状态到另一个状态的转换; 策略则几乎完全不知道其他策略的存在。

# 结构

状态设计模式的结构

  1. 上下文 (Context) 保存了对于一个具体状态对象的引用, 并会将所有与该状态相关的工作委派给它。 上下文通过状态接口与状态对象交互, 且会提供一个设置器用于传递新的状态对象。

  2. 状态 (State) 接口会声明特定于状态的方法。 这些方法应能被其他所有具体状态所理解, 因为你不希望某些状态所拥有的方法永远不会被调用。

  3. 具体状态 (Concrete States) 会自行实现特定于状态的方法。 为了避免多个状态中包含相似代码, 你可以提供一个封装有部分通用行为的中间抽象类。

    状态对象可存储对于上下文对象的反向引用。 状态可以通过该引用从上下文处获取所需信息, 并且能触发状态转移。

  4. 上下文和具体状态都可以设置上下文的下个状态, 并可通过替换连接到上下文的状态对象来完成实际的状态转换。

# 代码案例

最近王二狗又要过生日了,近两年他内心中是非常抗拒过生日的,因为每过一个生日就意味着自己又老一岁,离被辞退的35岁魔咒又近了一步。可惜时间是不以人的意志为转移的,任何人都阻止不了时间的流逝,所以该过还的过。令二狗比较欣慰的时,这次过生日老婆送了他一个自己一直想要的机械键盘作为生日礼物... 翠花于是在二狗生日前3天在京东上下了一个单...

自从下单以来,二狗天天看物流状态信息,心心念念着自己的机械键盘快点到...

这个物流系统就很适合使用状态模式来开发,因为此过程存在很多不同的状态,例如接单,出库,运输,送货,收货,评价等等。而订单在每个不同的状态下的操作可能都不一样,例如在接单状态下,商家就需要通知仓库拣货,通知用户等等操作,其他状态类似

下面是实例的UML类图 在这里插入图片描述

第一,定义一个状态接口

此接口定义各个状态的统一操作接口

public interface LogisticsState {
    void doAction(JdLogistics context);
}
1
2
3

第二,定义一个物流Context类

此类持有一个LogisticsState 的引用,负责在流程中保持并切换状态

public class JdLogistics {
    private LogisticsState logisticsState;

    public void setLogisticsState(LogisticsState logisticsState) {
        this.logisticsState = logisticsState;
    }

    public LogisticsState getLogisticsState() {
        return logisticsState;
    }

    public void doAction(){
        Objects.requireNonNull(logisticsState);
        logisticsState.doAction(this);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

第三,实现各种状态类

  • 接单状态类,其需要实现LogisticsState接口

    public class OrderState implements LogisticsState {
    @Override
    public void doAction(JdLogistics context) {
        System.out.println("商家已经接单,正在处理中...");
    }
    }
    
    1
    2
    3
    4
    5
    6
  • 出库状态类

    public class ProductOutState implements LogisticsState {
    @Override
    public void doAction(JdLogistics context) {
        System.out.println("商品已经出库...");
    }
    }
    
    1
    2
    3
    4
    5
    6

    依次类推,可以建立任意多个状态类

第四, 客户端使用

public class StateClient {

    public void buyKeyboard() {
        //状态的保持与切换者
        JdLogistics jdLogistics = new JdLogistics();

        //接单状态
        OrderState orderState = new OrderState();
        jdLogistics.setLogisticsState(orderState);
        jdLogistics.doAction();

        //出库状态
        ProductOutState productOutState = new ProductOutState();
        jdLogistics.setLogisticsState(productOutState);
        jdLogistics.doAction();

        //运输状态
        TransportState transportState = new TransportState();
        jdLogistics.setLogisticsState(transportState);
        jdLogistics.doAction();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

输出结果:

商家已经接单,正在处理中...
商品已经出库...
商品正在运往天津分发中心
1
2
3

可见,我们将每个状态下要做的具体动作封装到了每个状态类中,我们只需要切换不同的状态即可。如果不使用状态模式,我们的代码中可能会出现很长的if else列表,这样就不便于扩展和修改了。?

# 优缺点

  • 单一职责原则。 将与特定状态相关的代码放在单独的类中。

  • 开闭原则。 无需修改已有状态类和上下文就能引入新状态。

  • 通过消除臃肿的状态机条件语句简化上下文代码。

  • 如果状态机只有很少的几个状态, 或者很少发生改变, 那么应用该模式可能会显得小题大作。

编辑 (opens new window)
上次更新: 2024/02/22, 14:03:19
策略模式
类图关系类型

← 策略模式 类图关系类型→

最近更新
01
spark基础
02-22
02
mysql读写分离和分库分表
02-22
03
数据库迁移
02-22
更多文章>
Theme by Vdoing | Copyright © 2019-2024 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式