设计模式轻松学【十五】状态模式

简介: 在生活中,人的情绪往往都在变化,有高兴的时候和伤心的时候,不同的情绪有不同的行为,比如你开心的时候,会去大吃一顿,伤心的时候会出去唱K。外界也会影响我们的情绪,唱K之后你就变得开心了。这个情绪的变化我们可以看成一个对象的状态变化,状态改变时会有不同的操作。对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else 语句来做状态判断,再进行不同情况的处理。但当对象的状态很多时,程序会变得很复杂。这很像策略模式、但他们有着本质的区别。

特点与定义

  • 定义

    当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。

    我们可以这么理解:有些人一旦发生过什么事以后,就像变了个人似的。

  • 使用场景

    • 行为随状态的改变而改变。
    • 如果需要使用大量的条件、分支判断。
  • 参与角色

    • 环境角色(Context):也称为上下文,定义客户端需要的接口,维护一个当前状态,并且负责具体状态的切换。
    • 抽象状态角色(State):定义了每一个状态的行为集合,并且封装环境角色以实现状态切换。
    • 具体状态角色(ConcreteState):具体状态主要有两个职责:一是处理本状态下的事情,二是从本状态如何过渡到其他状态。
  • 类结构图

    image.png

  • 结构示例代码

    • 抽象状态类

      abstract class State {
          public abstract void handle(Context context);
      }
    • 具体状态类

      //具体状态A类
      class ConcreteStateA extends State {
          public void handle(Context context) {
              System.out.println("当前状态是 A,马上切换状态到B");
              context.setState(new ConcreteStateB());
          }
      }
      
      //具体状态B类
      class ConcreteStateB extends State {
          public void handle(Context context) {
              System.out.println("当前状态是 B,马上切换状态到A");
              context.setState(new ConcreteStateA());
          }
      }
    • 环境角色

      class Context {
          private State state;
      
          // 设置新状态
          public void setState(State state) {
              this.state = state;
          }
          // 读取状态
          public State getState() {
              return state;
          }
          // 对请求做处理,该handle可以有多个方法
          public void request() {
              state.handle(this);
          }
      }
    • 客户端调用

      public class StateTest {
          public static void main(String[] args) {
              Context context = new Context();
              //定义初始状态
              context.setState(new ConcreteStateA());
              context.request();
              context.request();
          }
      }
      //输出结果
      当前状态是 A,马上切换状态到B
      当前状态是 B,马上切换状态到A

模式案例分析

现在我们以电梯为案例来进行分析

电梯的状态有停止、运行、开门和关门等状态。而且每个状态还都要有特定的行为,比如在开门的状态下,电梯只能关门,而不能运行;在关门状态下,电梯可以运行、开门等。我们用一张表格来表示这个关系:

状态\动作 开门 关门 运行 停止
开门状态 X O X X
关门状态 O X O O
运行状态 X X X O
停止状态 O X O X

这种情况下,一般做法是先根据依赖倒置原则定义出接口,电梯一共有四种状态,所以定义了四个常量表示这几种状态,实现类中电梯的每一次动作发生都要对状态进行判断,看是否可以执行动作。

  1. 我们一般会这么实现

    • 电梯抽象类,维护了电梯状态和能够执行的行为

      interface ILift{
           //电梯的4个状态  
           //开门状态    
           int OPENING = 1; 
           //关门状态  
           int CLOSING = 2;  
           //运行状态  
           int RUNNING = 3; 
           //停止状态  
           int STOPPING = 4; 
           
           //设置电梯的状态  
           public void setState(int state);  
           
           //电梯的动作
           public void open();  
           public void close();  
           public void run();
           public void stop();
      }
    • 电梯子类,继承电梯规范,并实现一系列的动作,并负责状态的改变

      class SanLinGLift implements ILift{
          private int state;
      
          @Override
          public void setState(int state) {
              this.state = state;
          }
      
          //执行关门动作
          @Override
          public void close() {
              switch (this.state) {
                  case OPENING:
                      System.out.println("电梯关门了。。。");//只有开门状态可以关闭电梯门,可以对应电梯状态表来看
                      this.setState(CLOSING);//关门之后电梯就是关闭状态了
                      break;
                  case CLOSING:
                      //do nothing //已经是关门状态,不能关门
                      break;
                  case RUNNING:
                      //do nothing //运行时电梯门是关着的,不能关门
                      break;
                  case STOPPING:
                      //do nothing //停止时电梯也是关着的,不能关门
                      break;
              }
          }
      
          //执行开门动作
          @Override
          public void open() {
              switch (this.state) {
                  case OPENING://门已经开了,不能再开门了
                      //do nothing
                      break;
                  case CLOSING://关门状态,门打开:
                      System.out.println("电梯门打开了。。。");
                      this.setState(OPENING);
                      break;
                  case RUNNING:
                      //do nothing 运行时电梯不能开门
                      break;
                  case STOPPING:
                      System.out.println("电梯门开了。。。");//电梯停了,可以开门了
                      this.setState(OPENING);
                      break;
              }
          }
      
          //执行运行动作
          @Override
          public void run() {
              switch (this.state) {
                  case OPENING://电梯不能开着门就走
                      //do nothing
                      break;
                  case CLOSING://门关了,可以运行了
                      System.out.println("电梯开始运行了。。。");
                      this.setState(RUNNING);//现在是运行状态
                      break;
                  case RUNNING:
                      //do nothing 已经是运行状态了
                      break;
                  case STOPPING:
                      System.out.println("电梯开始运行了。。。");
                      this.setState(RUNNING);
                      break;
              }
          }
      
          //执行停止动作
          @Override
          public void stop() {
              switch (this.state) {
                  case OPENING: //开门的电梯已经是是停止的了(正常情况下)
                      //do nothing
                      break;
                  case CLOSING://关门时才可以停止
                      System.out.println("电梯停止了。。。");
                      this.setState(STOPPING);
                      break;
                  case RUNNING://运行时当然可以停止了
                      System.out.println("电梯停止了。。。");
                      this.setState(STOPPING);
                      break;
                  case STOPPING:
                      //do nothing
                      break;
              }
          }
          
      }
    • 客户端控制类

      public class StateTest {
          public static void main(String[] args) {
              ILift lift = new SanLinGLift();
              lift.setState(ILift.STOPPING);//电梯是停止的
              lift.open();//开门
              lift.close();//关门
              lift.run();//运行
              lift.stop();//停止
          }
      }
      
      //运行结果
      电梯门开了。。。
      电梯关门了。。。
      电梯开始运行了。。。
      电梯停止了。。。

    在上面这段代码中,我们非常棒的完成了电梯的运行操作。但是仔细分析你会发现,上述代码非常复杂,可扩展性也非常差,如果需求发生改变,我们要调整某个状态,所有的代码都会发生改变,这违背了开闭原则。

  2. 使用状态模式重构上述代码

    在上述代码中,我们需要分清楚状态和状态的切换,暂且理解为状态和动作

    根据状态模式,我们可以将上述动作进行封装成具体的状态对象,然后再采用Context进行协调切换

    • 定义抽象状态

      //状态抽象类
      abstract class LiftState {
          //Lift为上下文,即电梯本身
          public abstract void open(Lift lift);
          public abstract void close(Lift lift);
          public abstract void run(Lift lift);
          public abstract void stop(Lift lift);
          
      }
    • 定义Context实现切换

      //环境角色,上下文主要是控制这个状态
      class Lift {
          private LiftState liftState;
      
          // 设置新状态
          public void setState(LiftState liftState) {
              this.liftState = liftState;
          }
          // 读取状态
          public LiftState getLiftState() {
              return liftState;
          }
          
          //电梯开门行为
          public void open() {
              liftState.open(this);
          }
          
          //电梯关门行为
          public void close() {
              liftState.close(this);
          }
          
          //电梯运行行为
          public void run() {
              liftState.run(this);
          }
          
          //电梯停止行为
          public void stop() {
              liftState.stop(this);
          }
      }
    • 定义具体状态

      //开门状态
      class OpenningState extends LiftState {
      
          @Override
          public void open(Lift lift) {
              //什么都不用做,因为门已经开了
          }
      
          @Override
          public void close(Lift lift) {
              System.out.println("开门的状态下,我们可以关门....电梯门关了");
              //关门之后,我们将状态设置成
              lift.setState(new ClosingState());
          }
      
          @Override
          public void run(Lift lift) {
              //开门状态,不能运行
          }
      
          @Override
          public void stop(Lift lift) {
              System.out.println("开门的状态下,我们可以停止电梯....电梯停止了");
              lift.setState(new StoppingState());
          }
      }
      
      //关门状态
      class ClosingState extends LiftState {
      
          @Override
          public void open(Lift lift) {
              System.out.println("关门的状态下,我们可以开门....电梯门开了");
              lift.setState(new ClosingState());
          }
      
          @Override
          public void close(Lift lift) {
              //已经是关门状态,无法再次关门
          }
      
          @Override
          public void run(Lift lift) {
              System.out.println("关门的状态下,我们运行电梯....电梯运行了");
              lift.setState(new RunningState());
          }
      
          @Override
          public void stop(Lift lift) {
              System.out.println("关门的状态下,我们停止电梯....电梯停止了");
              lift.setState(new StoppingState());
          }
      }
      
      //运行状态
      class RunningState extends LiftState {
      
          @Override
          public void open(Lift lift) {
              //运行状态不能够开门
          }
      
          @Override
          public void close(Lift lift) {
              //运行状态,电梯门肯定是关闭的
          }
      
          @Override
          public void run(Lift lift) {
              //已经是运行状态,不做操作
          }
      
          @Override
          public void stop(Lift lift) {
              System.out.println("运行的状态下,我们停止电梯....电梯停止了");
              lift.setState(new StoppingState());
          }
      }
      
      //停止状态
      class StoppingState extends LiftState {
      
          @Override
          public void open(Lift lift) {
              System.out.println("停止的状态下,我们打开门....电梯门开了");
              lift.setState(new OpenningState());
          }
      
          @Override
          public void close(Lift lift) {
              //停止是由电梯运行后停止的,所以电梯门是开着的
          }
      
          @Override
          public void run(Lift lift) {
              System.out.println("停止的状态下,再次运行....电梯继续运行了");
              lift.setState(new RunningState());
          }
      
          @Override
          public void stop(Lift lift) {
              //已经是停止状态,无法再次停止
          }
      }
    • 客户端调用

      public class StateTest {
          
          public static void main(String[] args) {
              Lift lift = new Lift();
              //设置第一次执行时的初始状态
              lift.setState(new StoppingState());
              //执行动作,无需由客户端手动干预状态
              lift.run();
              lift.stop();
              lift.open();
              lift.close();
          }
      }
    • 非正常情况调用

      public class StateTest {
          
          public static void main(String[] args) {
              Lift lift = new Lift();
              //设置第一次执行时的初始状态
              lift.setState(new RunningState());
              //运行的状态下直接开门
              lift.open();
          }
      }
  3. 总结

    可以看到在状态模式实现的代码中,将以往的if-else条件判断转换为了四个相关的状态类来处理,客户端在使用的时候,无需关心当前是哪个状态,它只要执行具体的行为就可以,至于这些行为是否可以在当前状态下得到正确的响应完全由当前的状态类内部去处理。

理解状态和行为

状态就是当前环境所处的情况或者是场景,在不同场景下当前环境对于不同行为要给出不同的响应,具体在代码中的体现可以理解为就是你的if-else判断。而行为就是你要执行的动作,你要做的事情,你要给出的响应。例如人冷了要加衣服,热了要脱衣服,冷热就是状态,而加衣脱衣就是行为。

image.png

总结

  • 优点

    结构清晰,避免了过多的 switch- case 或者 if- else 语句的使用,避免了程序的复杂性,提高系统的可维护性。
    封装性强,遵循设计原则,很好地体现了开闭原则和单一职责原则,每个状态都有一个子类负责,你要增加状态就要增加子类,你要修改状态,你只修改一个子类就可以了。另外也体现了迪米特法则,状态变换完全放到类的内部来实现,屏蔽了客户端调用的时候对状态处理的感知。

  • 缺点

    缺点很明显,那就是子类会太多,因为每一个状态都对应一个子类,所以当你的状态非常多的时候就会发生类膨胀。

相关文章
|
2天前
|
设计模式 JavaScript Java
[设计模式Java实现附plantuml源码~行为型] 对象状态及其转换——状态模式
[设计模式Java实现附plantuml源码~行为型] 对象状态及其转换——状态模式
|
4月前
|
设计模式 Java 开发者
Java设计模式【二十一】:状态模式
Java设计模式【二十一】:状态模式
23 0
|
18天前
|
设计模式 Java
23种设计模式,状态模式的概念优缺点以及JAVA代码举例
【4月更文挑战第9天】状态模式是一种行为设计模式,允许一个对象在其内部状态改变时改变它的行为,这个对象看起来似乎修改了它的类。
29 4
|
1月前
|
设计模式
【设计模式】状态模式
【设计模式】状态模式
|
2月前
|
设计模式 Java 测试技术
浅谈设计模式 - 状态模式(十三)
浅谈设计模式 - 状态模式(十三)
17 0
|
3月前
|
设计模式 程序员
设计模式-状态模式(State)
设计模式-状态模式(State)
38 0
|
3月前
|
设计模式 Java
聊聊Java设计模式-状态模式
状态模式(State Pattern)指允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
52 1
|
3月前
|
设计模式 Go 开发工具
Golang设计模式——14状态模式
Golang设计模式——14状态模式
25 0
|
3月前
|
设计模式 前端开发
【设计模式】之状态模式
状态模式是一种非常有用的设计模式,在前端开发中经常用于管理应用状态和处理复杂的流程。它通过将对象行为和属性与特定条件下的处理逻辑分离开来,提高了代码的可维护性和可扩展性。通过使用状态模式,我们可以优雅地管理应用状态,并根据不同的条件来改变对象行为。然而,在应用状态模式时需要权衡其带来的优缺点,并根据具体情况进行选择。
39 1
|
4月前
|
设计模式
二十三种设计模式全面解析-深入探讨状态模式的高级应用技术:释放对象行为的无限可能
二十三种设计模式全面解析-深入探讨状态模式的高级应用技术:释放对象行为的无限可能