• 状态模式-将状态和行为封装成对象


    公号:码农充电站pro
    主页:https://codeshellme.github.io

    本篇文章来介绍状态模式State Design Pattern),状态模式常用来实现状态机,状态机常用在游戏开发等领域。

    1,状态模式

    状态模式的定义为:允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。

    状态模式将状态和行为封装成对象,不同的对象有着不同的行为。对象的状态会因某个行为的发生而改变,对象的状态一旦改变,那么对象的行为也会发生改变。

    对象的状态和行为,可以用下面这个图来解释。假如一个事物有三种状态 1,2,3,状态之间的转换关系如下:

    在这里插入图片描述

    在上面的状态转换图中,每种状态对应着不同的行为:

    • 状态 1:有两种行为 ab
      • 状态 1 经过 a 行为可转换到状态 2
      • 状态 1 经过 b 行为可转换到状态 3
    • 状态 2:有两种行为 cd
      • 状态 2 经过 c 行为可转换到状态 1
      • 状态 2 经过 d 行为可转换到状态 3
    • 状态 3:有一种行为 e
      • 状态 3 经过 e 行为可转换到状态 1

    状态模式的类图如下:

    在这里插入图片描述

    State 接口定义了状态可能拥有的所有行为,每个具体的状态都实现了这个接口,这样就使得状态之间可以互相替换。

    每个具体状态对 State 接口中的每个行为的实现是不一样的,这就相当于每个具体状态的行为是不一样的。

    StateMachine 是一个状态机,它拥有着一个状态对象,这个状态对象会不断的改变。

    2,游戏需求

    假设我们要为一款游戏中的角色编写状态转换的程序,并且游戏角色有积分:

    在这里插入图片描述

    该游戏中的角色共有 4 种状态 A,B,C,D,共有 3 种操作 x,y,z

    • 状态 A:只能进行 x 操作,转化到状态 B
      • 状态 A 为初始状态
    • 状态 B:有两种操作:
      • x 操作:转化到状态 C
      • y 操作:转化到状态 D
    • 状态 C:有两种操作
      • x 操作:转化到状态 D
      • z 操作:转化到状态 A
    • 状态 D:只能进行 z 操作,转化到状态 C

    积分变化:

    • 操作 x 会使角色增加 100 积分
    • 操作 y 会使角色增加 200 积分
    • 操作 z 会使角色减少 50 积分

    3,编写代码

    下面我们使用状态模式来编写角色的状态转换程序。

    首先根据状态模式的类图,我们需要有一个 State 接口,该接口包含角色所有的操作,并且包含一个状态机的引用。

    这里我将 State 作为一个抽象类,每个操作的默认实现是 do nothing,每个具体状态可以根据自己的需要进行覆盖。

    代码如下:

    abstract class State {
        protected String stateName;
        protected RoleStateMachine machine;
        
        void x() {
            // do nothing
        }
        
        void y() {
            // do nothing
        }
        
        void z() {
            // do nothing
        }
    
        // 获取当前状态名
        public String getStateName() {
            return stateName;
        }
    }
    

    接下来编写角色状态机类,代码中也都写了注释:

    class RoleStateMachine {
        private State currentState; // 当前状态
        private int score;          // 积分
    
        public RoleStateMachine() {
            this.score = 0; // 初始积分为 0
            // 初始状态为 A
            this.currentState = new StateA(this);
        }
    
        // 当发生某个操作时需要转化到相应的状态
        // 用该方法进行设置
        public void setCurrentState(State state) {
            currentState = state;
        }
    
        // 获取当前状态
        public String getCurrentState() {
            return currentState.getStateName();
        }
    
        // 获取积分
        public int getScore() {
            return score;
        }
    
        // 增加积分
        public void addScore(int score) {
            this.score += score;
        }
        
        // 减少积分
        public void delScore(int score) {
            this.score -= score;
        }
    
        // 状态机中也包含状态中的所有操作
        // 每个操作都委托给当前状态的相应操作来完成
    
        public void x() {
            currentState.x();
        }
    
        public void y() {
            currentState.y();
        }
    
        public void z() {
            currentState.z();
        }
    }
    

    下面编写 4 个状态类,每个状态类都继承 State 接口,并且每个状态类中要持有一个状态机的引用,由构造函数引入:

    class StateA extends State {
        public StateA(RoleStateMachine machine) {
            this.machine = machine;
            this.stateName = "StateA";
        }
    
        public void x() {
            machine.addScore(100);
            machine.setCurrentState(new StateB(machine));
        }
    }
    
    class StateB extends State {
        public StateB(RoleStateMachine machine) {
            this.machine = machine;
            this.stateName = "StateB";
        }
    
        public void x() {
            machine.addScore(100);
            machine.setCurrentState(new StateC(machine));
        }
    
        public void y() {
            machine.addScore(200);
            machine.setCurrentState(new StateD(machine));
        }
    }
    
    class StateC extends State {
        public StateC(RoleStateMachine machine) {
            this.machine = machine;
            this.stateName = "StateC";
        }
    
        public void x() {
            machine.addScore(100);
            machine.setCurrentState(new StateD(machine));
        }
    
        public void z() {
            machine.delScore(50);
            machine.setCurrentState(new StateA(machine));
        }
    }
    
    class StateD extends State {
        public StateD(RoleStateMachine machine) {
            this.machine = machine;
            this.stateName = "StateD";
        }
    
        public void z() {
            machine.delScore(50);
            machine.setCurrentState(new StateC(machine));
        }
    }
    

    4,测试代码

    下面来测试代码:

    RoleStateMachine role = new RoleStateMachine();
    
    // 初始状态为 StateA,积分为 0
    assert role.getCurrentState().equals("StateA");
    assert role.getScore() == 0;
    
    role.y(); // 在状态 A 进行 y 操作
    
    // 在状态 A 时,没有 y 操作
    // 所以如果进行 y 操作,状态和积分都保持不变
    assert role.getCurrentState().equals("StateA");
    assert role.getScore() == 0;
    
    role.x(); // 在状态 A 进行 x 操作
    assert role.getCurrentState().equals("StateB");
    assert role.getScore() == 100;
    
    role.y(); // 在状态 B,进行 y 操作
    assert role.getCurrentState().equals("StateD");
    assert role.getScore() == 300;
    
    role.z(); // 在状态 D,进行 z 操作
    assert role.getCurrentState().equals("StateC");
    assert role.getScore() == 250;
    
    role.z(); // 在状态 C,进行 z 操作
    assert role.getCurrentState().equals("StateA");
    assert role.getScore() == 200;
    
    System.out.println("Test OK.");
    

    注意,使用 Java assert 时,记得用 -ea 参数打开断言功能。

    我将完整的代码放在了这里,供大家参考。

    5,总结

    状态模式将状态和行为封装成对象,不同的状态有着不同的行为。这种设计使得处理状态转换这一类的逻辑变得非常有条理,而且不易出错。

    (本节完。)


    推荐阅读:

    命令模式-将请求封装成对象

    适配器模式-让不兼容的接口得以适配

    外观模式-简化子系统的复杂性

    模板方法模式-封装一套算法流程

    迭代器模式-统一集合的遍历方式


    欢迎关注作者公众号,获取更多技术干货。

    码农充电站pro

  • 相关阅读:
    【2020-11-16】就是自己的松散意识在作怪
    JQuery 事件
    JQuery DOM 有关代码练习
    JQuery中的DOM操作
    主题简介 ASP .NET
    JQuery 选择器 *很重要 多记
    JQuery 基础
    Ajax 获取数据代码
    Ajax 介绍
    JavaScript 基础二
  • 原文地址:https://www.cnblogs.com/codeshell/p/14250233.html
Copyright © 2020-2023  润新知