:::info 状态模式在日常开发中是一个非常实用的模式,可以将你的代码逼格迅速提升一个档次,所以让我们开始今天的卓越之旅吧。 :::

定义

允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。

使用场景

你发现你的代码里面存在一个很长的if else列表,而这些分支都是因为不同状态下执行的操作不一样时考虑使用此模式。

UML

状态模式 - 图1

角色结构

  • State:是一个接口,封装了状态及其行为。
  • ConcreteState X:State的实现类,表示具体的状态。
  • Context:保持并切换各个状态,其持有一个State的引用。它将依赖状态的各种操作委托给不同的状态对象执行。其负责与客户端交互。

    优点

  • 增强了程序的可扩展性,因为我们很容易添加一个State

  • 增强了程序的封装性,每个状态的操作都被封装到了一个状态类中

    缺点

  • 状态模式的使用必然会增加系统类和对象的个数。

  • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  • 状态模式对”开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

    业务场景

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

    代码示例

    LogisticsState 定义一个状态接口

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

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

    JdLogistics 定义一个物流Context类

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

    1. public class JdLogistics {
    2. private LogisticsState logisticsState;
    3. public void setLogisticsState(LogisticsState logisticsState) {
    4. this.logisticsState = logisticsState;
    5. }
    6. public LogisticsState getLogisticsState() {
    7. return logisticsState;
    8. }
    9. public void doAction() {
    10. Objects.requireNonNull(logisticsState);
    11. logisticsState.doAction(this);
    12. }
    13. }

    OrderState ProductOutState TransportState 实现各种状态类

    1. public class OrderState implements LogisticsState {
    2. @Override
    3. public void doAction(JdLogistics context) {
    4. System.out.println("商家已经接单,正在处理中...");
    5. }
    6. }
    1. public class ProductOutState implements LogisticsState {
    2. @Override
    3. public void doAction(JdLogistics context) {
    4. System.out.println("商品已经出库...");
    5. }
    6. }
    1. public class TransportState implements LogisticsState {
    2. @Override
    3. public void doAction(JdLogistics context) {
    4. System.out.println("商品正在运往西安分发中心");
    5. }
    6. }

    Client

    1. public class Client {
    2. public static void main(String[] args) {
    3. //状态的保持与切换者
    4. JdLogistics jdLogistics = new JdLogistics();
    5. //接单状态
    6. OrderState orderState = new OrderState();
    7. jdLogistics.setLogisticsState(orderState);
    8. jdLogistics.doAction();
    9. //出库状态
    10. ProductOutState productOutState = new ProductOutState();
    11. jdLogistics.setLogisticsState(productOutState);
    12. jdLogistics.doAction();
    13. //运输状态
    14. TransportState transportState = new TransportState();
    15. jdLogistics.setLogisticsState(transportState);
    16. jdLogistics.doAction();
    17. }
    18. }

    输出

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

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

    技术要点总结

  • 必须要有一个Context类,这个类持有State接口,负责保持并切换当前的状态。

  • 状态模式没有定义在哪里进行状态转换,本例是在Context类进行的,也有人在具体的State类中转换

当使用Context类切换状态时,状态类之间互相不认识,他们直接的依赖关系应该由客户端负责。
例如,只有在接单状态的操作完成后才应该切换到出库状态,那么出库状态就对接单状态有了依赖,这个依赖顺序应该由客户端负责,而不是在状态内判断。

当使用具体的State类切换时,状态直接就可能互相认识,一个状态执行完就自动切换到了另一个状态去了

状态模式与策略模式的区别

状态模式与策略模式的UML类图都是一样的,从表面上看他们非常相似。特别是将状态切换任务放在Context中做的时候就更像了,但是其背后的思想却非常不同。

  • 策略模式定义了一组可互相代替的算法,这一组算法对象完成的是同一个任务,只是使用的方式不同,例如同样是亿万富翁,马云通过卖东西实现,而王思聪通过继承实现。
  • 状态模式不同的状态完成的任务完全不一样。