1. 模式定义

  • 状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换❕%%
    0724-🏡⭐️◼️主要解决的问题是什么?🔜📝 对象在不同状态下转换时输出不同行为的问题◼️⭐️-point-202301250724%%
  • 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
  • 对==有状态的对象==,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为

2. 模式结构⭐️🔴

2.1. 模式角色

  • 环境(Context)角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
  • 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
  • 具体状态(Concrete State)角色:实现抽象状态所对应的行为。
    %%
    0729-🏡⭐️◼️所有有 context 角色的设计模式?🔜📝 策略模式、状态模式、解释器模式◼️⭐️-point-202301250729%%

2.2. UML

image.png

2.3. 实现逻辑

3. 案例分析⭐️🔴

3.1. 电梯运行

通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可以执行开门操作。

3.1.1. 传统方案

  • 会需要大量的 switch…case 这样的判断(ifelse 也是一样),使程序的可阅读性变差。
  • 扩展性很差。

image.png

3.1.2. 状态模式

3.1.3. 实现逻辑⭐️🔴

Context 与 ConcreteState 相互聚合 +setter 导入

  1. 环境类 Context 聚合四种具体状态类
  2. 环境类 Context聚合抽象状态类 LiftState,并setter 导入,通过 LiftState 抽象状态类最终调用具体状态类的 4 种方法
  3. 抽象状态类 LiftState 又聚合了环境类 Context,并setter 导入,方便子类使用
  4. Client 首先要通过 Context 的 setLiftState传入一个具体状态类,来设置当前状态
  5. Client 中通过 Context 调用步骤 4 中传入的具体状态类实现的方法
  6. 在具体状态类中通过 (继承的抽象状态类LiftState中所聚合的)context 来改变 Context 中聚合的 liftState 的状态,然后通过调用 liftState 的方法,通过多态会调用刚刚设置 ConcreteState 的方法,从而达到状态流转
    %%
    1724-🏡⭐️◼️状态模式状态流转的逻辑关键点在哪?client 调用 context 的 setState 方法之后,调用 context 的具体方法时,调用的是 context 中聚合的抽象 state 类中的方法,而 client 调用 setState 已经初始化了状态,也就指定了具体 state 类,所以就会调用具体 state 类的某个方法 (比如 open),然后在这个具体 state 类中,会调用 context 的 change state 方法,因为从父类中继承过来,所以能拿到 context 的调用权限。改变了状态后,具体的 state 类也就指定了,然后在 open 方法中又调用了 context 的 close 方法,最终调用的是刚刚指定的状态类的 close 方法,至此状态跳转逻辑就流转起来了。关键在于具体状态类继承抽象状态类,然后 context 和抽象状态类相互聚合。◼️⭐️-point-202301241724%%

    image.png

image.png

3.1.4. 示例代码

[[pages/002-schdule/001-Arch/001-Subject/013-DemoCode/design_patterns/src/main/java/com/itheima/pattern/state/after/LiftState.java]]

3.2. APP 抽奖问题⭐️🔴

%%
▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230410-1021%%
❕ ^b4joft

  • 1)应用实例要求完成 APP 抽奖活动项目,使用状态模式
  • 2)思路分析和图解(类图)
    • 定义出一个接口叫状态接口,每个状态都实现它
    • 接口有扣除积分方法、抽奖方法、发放奖品方法

3.2.1. UML

3.2.2. 实现逻辑

3.2.2.1. 角色分析

环境角色:RaffleActivity
抽象状态:State
具体状态:4 种抽奖状态,DispenseOutState、CanRaffleState、DispenseOutState、NoRaffleState

3.2.2.2. 关系逻辑

  1. 环境角色 RaffleActivity 聚合了抽象状态 State,根据业务逻辑调用 State 不同实现类的方法
  2. 具体状态类【聚合 + 构造导入】环境类 RaffleActivity,并调用方法
    环境类 RaffleActivity 也可以放到抽象状态类中聚合,跟案例 1 一样,会更加优雅
  3. 具体状态类通过改变环境角色类 RaffleActivity 的状态,以及调用其方法来达到状态流转的目的

3.2.3. 示例代码

[[RaffleActivity.java]]

3.3. 借贷平台

借贷平台的订单,有审核 - 发布 - 抢单等等步骤,随着操作的不同,会改变订单的状态,项目中的这个模块实现就会使用到状态模式

3.3.1. 传统方案

通常通过 if/else 判断订单的状态,从而实现不同的逻辑,伪代码如下

1
2
3
4
5
6
7
if(审核){
//审核逻辑
}else if(发布){
//发布逻辑
}else if(接单){
//接单逻辑
}

3.3.1.1. 存在问题⭐️🔴

这类代码难以应对变化,在添加一种状态时,我们需要手动添加 if/else,在添加一种功能时,要对所有的状态进行判断。因此代码会变得越来越臃肿,并且一旦没有处理某个状态,便会发生极其严重的 BUG,难以维护

3.3.2. 状态模式

image.png

3.3.3. 实现逻辑

与前面 2 个案例的不同之处在于具体状态类和抽象状态类都没有聚合 Context,而是通过调用方法时传入的,其他逻辑差不多

4. 适用场景⭐️🔴

  1. 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
  2. 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
    %%
    0739-🏡⭐️◼️状态模式适用场景:1. 对象行为取决于其状态;2. 庞大的业务分支取决于对象状态◼️⭐️-point-202301250739%%

5. 优缺点⭐️🔴

5.1. 优点

  1. 高可读性:将每个状态的行为封装到对应的一个类中,条理清晰
  2. 易维护:将状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块

5.2. 缺点

  1. 状态模式的使用必然会增加系统类和对象的个数
  2. 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱Context 和抽象状态类相互聚合 +setter 导入
  3. 状态模式对 “ 开闭原则 “ 的支持并不太好。因为具体状态类修改或增加后,context 也要修改或增加对应状态对象,比如状态常量,对状态对象方法的调用,以及状态流转中涉及到的其他状态类也要修改

6. JDK 源码分析

7. 实战经验

8. 参考与感谢

设计模式-2、设计模式及设计原则