请自学状态模式,并从模式特点与定义、模式应用场景、模式案例及代码分析、模式优缺点四个方面阐述状态模式。


状态(State)模式的定义:状态模式是属于行为模式家族的一种设计模式。它通常用于面向对象编程中,它使对象能够根据其内部状态的变化改变其行为。对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。


状态模式的关键特征:

1.Context:这个类将维护一个State子类的实例。它为客户端提供了与对象交互的接口,并处理来自客户端的请求。context类有一个或多个状态变量,可以改变对象的状态。

2.State:这是一个抽象类,定义了上下文对象所有可能状态的接口。这个类的每个具体实现都表示上下文的一个特定状态。

3.具体状态:这些是实现State接口的子类。它们表示上下文对象的不同状态,并提供State接口中定义的方法的特定实现。

4.基于接口的设计:在状态模式中,类是围绕接口而不是具体实现设计的。这带来了灵活的代码结构,并使得向对象添加新状态或删除现有状态变得容易。

5.状态封装:每个状态封装了它的行为,这意味着上下文对象不需要知道每个状态的行为。这使得代码可以更好地组织,更容易维护。

总的来说,状态模式通过在上下文及其状态之间提供清晰的关注点分离,支持健壮和可扩展的软件设计。


状态模式是一种对象行为型模式,其主要优点如下:

1.状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。

2.减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。

3.有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。


状态模式的主要缺点如下:

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

2.状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。


状态模式的应用场景:

(1)行为随状态改变而改变的场景。

(2)一个操作中含有庞大的多分支结构,并且这些分支取决于对象的状态。

1.自动贩卖机:自动贩卖机可以有多种状态,如“没有硬币插入”,“硬币插入”,“分配项目”等。自动售货机的行为根据这些状态而变化,状态模式可用于实现此行为。

2.交通信号:交通信号可以有不同的状态,如“绿”、“黄”、“红”等。交通信号的行为基于这些状态发生变化,状态模式可以用来定义每个状态的行为。

3.文档编辑:文本编辑器可以有多种编辑模式,如“插入”、“覆盖”、“选择”等。编辑器的行为会根据当前模式而改变,状态模式可以用于在这些模式之间切换。

4.游戏开发:在许多游戏中,角色可以有不同的状态,如“空闲”、“行走”、“跳跃”、“攻击”等。角色的行为基于这些状态而变化,状态模式可以用来定义每个状态的行为。

5.TCP连接:TCP连接可以有不同的状态,例如EstablishedFin-wait-1Close-wait等。连接的行为会根据这些状态而改变,状态模式可以用来实现这种行为。


状态模式的具体案例及代码分析:

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

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

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

类图: image.png

第一,定义一个状态接口

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

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

第二,定义一个物流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);
   }
}



第三,实现各种状态类

接单状态类,其需要实现

LogisticsState

接口

public class OrderState implements LogisticsState {
   @Override
   public void doAction(JdLogistics context) {
       System.out.println("商家已经接单,正在处理中...");
   }
}

出库状态类

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

运输状态类

public class TransportState implements LogisticsState{
   @Override
   public void doAction(JdLogistics context) {
       System.out.println("快递正在运输中..");
   }
}

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

第四, 客户端使用

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();
   }
}

输出结果:

image.png

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


技术要点总结

  1. 必须要有一个Context类,这个类持有State接口,负责保持并切换当前的状态。
  2. 状态模式没有定义在哪里进行状态转换,本例是在Context类进行的,也有人在具体的State类中转换
  3. 当使用Context类切换状态时,状态类之间互相不认识,他们直接的依赖关系应该由客户端负责。 例如,只有在接单状态的操作完成后才应该切换到出库状态,那么出库状态就对接单状态有了依赖,这个依赖顺序应该由客户端负责,而不是在状态内判断。
  4. 当使用具体的State类切换时,状态直接就可能互相认识,一个状态执行完就自动切换到了另一个状态去了

优点

  1. 增强了程序的可扩展性,因为我们很容易添加一个State
  2. 增强了程序的封装性,每个状态的操作都被封装到了一个状态类中

缺点 类变多了