状态模式

模式说明

一个对象根据自身属性的变化而做出相应的动作,生活中该场景很常见,例如根据心情的不同,人(心情是人的属性)会做出不同的事情,例如高兴时手舞足蹈,悲伤时哭泣等等。在客户端实现此类场景时,最简单的做法是if-elseswitch-case做分支判断/选择。缺点是显而易见的,当变量变化的种类增加时,对象需要相应地增加处理分支,又或者某一变化的动作需要修改时,也要改动源码,这将违背开闭原则。客户端责任过多,违背单一职责原则,另外程序也不易扩展。对于此类场景,可以使用状态模式实现。以一个环境类(上下文类)代表前述对象,其内部持有当前状态属性值和当前状态实例。环境类主要维护三个方法,状态属性值设置方法,状态实例设置方法,对应当前状态的响应方法,该响应方法内部调用不同状态类的响应方法。不同状态单独成类,均继承自一个抽象状态类。客户端使用时,声明环境类,设置状态值后执行环境类的响应方法。方法内实际调用由声明环境类时通过构造器初始化的默认状态实例的响应方法。每个状态类的响应方法会判断状态值,符合本状态实例的要求就处理,否则将自身状态修改为下一个状态(新状态实例赋值给状态对象属性)。如此就可以实现状态的自动转移,直到某个能响应的状态或到最后一个状态也未能响应,处理结束。

状态模式与责任链模式比较 与责任链模式的相似点是都有处理传递的动作,不同之处是责任链模式中所有层级的处理者对象共存,从低往高传。状态模式则是一个环境对象从某个状态开始响应状态值,在当前状态不能响应的情况下,自身状态改变为下一状态,不存在多状态共存的情况

本示例以根据分数定等级的场景为例,演示如下内容。客户端为一个分数评级类(环境类) ScoreLevel设置分数后,执行该评级类的响应方法queryLevel返回该分数对应的等级。

结构

抽象状态类: 持有状态的一些属性,定义一个状态响应方法。 具体状态类: 继承抽象状态类,实现抽象状态类的抽象方法。结合传入的分数评级类(环境类)判断响应或转移状态。 环境类(上下文类): 持有当前状态值和当前状态实例。维护四个方法,getScore/setScoresetScoreState用于设置当前状态实例,queryLevel响应当前状态,返回评级结果。

代码演示

package com.yukiyama.pattern.behavior;

/**
 * 状态模式
 */
public class StateDemo {

    public static void main(String[] args) {
        // 声明一个环境类
        ScoreLevel scoreLevel = new ScoreLevel();
        // 给出当前状态值
        scoreLevel.setScore(50);
        // 输出“该成绩等级为D。”
        String level1 = scoreLevel.queryLevel();
        System.out.printf("该成绩等级为%s。\n", level1);
        
        scoreLevel.setScore(60);
        String level2 = scoreLevel.queryLevel();
        // 输出“该成绩等级为C。”
        System.out.printf("该成绩等级为%s。\n", level2);
        
        scoreLevel.setScore(80);
        String level3 = scoreLevel.queryLevel();
        // 输出“该成绩等级为B。”
        System.out.printf("该成绩等级为%s。\n", level3);
        
        scoreLevel.setScore(90);
        String level4 = scoreLevel.queryLevel();
        // 输出“该成绩等级为A。”
        System.out.printf("该成绩等级为%s。\n", level4);
    }

}

/**
 * 抽象状态类
 * 持有状态的一些属性,定义一个状态响应方法,参数是环境类实例。
 * 本示例只有一个等级属性。
 */
abstract class ScoreState{
    protected String level;
    public abstract String queryLevel(ScoreLevel sl);
}

/**
 * 具体状态类
 * 实现抽象状态类的抽象方法。通过传入的分数评级类(环境类)获取到当前状态值
 * (分数),判断是否可以响应,可以时返回响应结果,否则调用分数评级类的的
 * setScoreState方法,将当前状态实例设置为C等级状态(状态转移)。
 * 下例是D等级的状态类,为D对应的分数做出响应,令level属性值为D并返回。
 * 判断不能响应时将状态改为C等级状态。
 */
class DScoreState extends ScoreState{
    @Override
    public String queryLevel(ScoreLevel sl) {
        if(sl.getScore() < 60) {
            level = "D";
            return level;
        } else {
            sl.setScoreState(new CScoreState());
            return sl.queryLevel();
        }
    }
}

/**
 * 具体状态类
 * 下例是C等级的状态类,为C对应的分数做出响应,令level属性值为C并返回。
 * 判断不能响应时将状态改为B等级状态。
 */
class CScoreState extends ScoreState{
    @Override
    public String queryLevel(ScoreLevel sl) {
        if(sl.getScore() >= 60 && sl.getScore() < 80) {
            level = "C";
            return level;
        } else {
            sl.setScoreState(new BScoreState());
            return sl.queryLevel();
        }
    }
}

/**
 * 具体状态类
 * 下例是B等级的状态类,为B对应的分数做出响应,令level属性值为B并返回。
 * 判断不能响应时将状态改为A等级状态。
 */
class BScoreState extends ScoreState{
    @Override
    public String queryLevel(ScoreLevel sl) {
        if(sl.getScore() >= 80 && sl.getScore() < 90) {
            level = "B";
            return level;
        } else {
            sl.setScoreState(new AScoreState());
            return sl.queryLevel();
        }
    }
}

/**
 * 具体状态类
 * 下例是A等级的状态类,为A对应的分数做出响应,令level属性值为A并返回。
 * 最终状态,无转移。
 */
class AScoreState extends ScoreState{
    @Override
    public String queryLevel(ScoreLevel sl) {
        level = "A";
        return level;
    }
}

/**
 * 环境类(上下文类)
 * 持有当前状态值int score和当前状态实例ScoreState state。通过无参
 * 构造器初始化state为DScoreState。维护四个方法,getScore/setScore
 * 方法是score的getter/setter;setScoreState用于设置当前状态实例,
 * 状态转移时使用;queryLevel是状态响应方法,通过调用当前状态实例的
 * queryLevel方法返回评级结果。
 * 下例是一个分数评级类。
 */
class ScoreLevel{
    private int score;
    private ScoreState state;
    
    // 通过构造器初始化当前状态为DScoreState
    public ScoreLevel(){
        state = new DScoreState();
    }
    public int getScore() {
        return score;
    }
    public void setScore(int score) {
        if(score >= 0 && score <= 100) {
            this.score = score;
        } else {
            System.out.println("分数输入有误。");
        }
    }
    public void setScoreState(ScoreState state) {
        this.state = state;
    }
    public String queryLevel() {
        return state.queryLevel(this);
    }
}

Last updated