状态模式

113 阅读3分钟

背景

在State 模式中,我们用类表示状态。以类来表示状态后,我们就能通过切换类来方便地改变对象的状态

登场角色

State 状态

State 角色表示状态,定义了不同状态进行不同处理接口

ConcreteState 具体的状态

ConcreteState 角色表示各个具体的状态,它实现了State接口

Context 状况,前后关系,上下文

Context 角色持有表示当前状态的 ConcreteState 角色,此外,他还定义了供外部调用者使用State 模式的接口

类图

示例代码

实例代码是模拟了一个金库报警系统,在白天和晚上的情况下,使用金库,打电话,使用警铃的方式会有所不同

State 接口

public interface State {

    // 设置时间
    void doClock(Context context, int hour);

    // 使用金库
    void doUse(Context context);

    // 使用警报
    void doAlarm(Context context);

    // 使用电话
    void doPhone(Context context);
}

DayState

public class DayState implements State {

    private static DayState dayState = new DayState();

    private DayState() {
    }

    public static DayState getInstance() {
        return dayState;
    }

    @Override
    public void doClock(Context context, int hour) { // 设置时间
        if (hour < 9 || hour > 17) {
            context.changeState(NightState.getInstance());
        }
    }

    @Override
    public void doUse(Context context) { // 使用金库
        context.recordLog("使用金库 [白天]");
    }

    @Override
    public void doAlarm(Context context) { // 使用警报
        context.callSecurityCenter("按下警铃 [白天]");
    }

    @Override
    public void doPhone(Context context) { // 使用电话
        context.callSecurityCenter("正常通话 [白天]");
    }
}

NightState

public class NightState implements State {

    private static NightState nightState =  new NightState();

    private NightState() {
    }

    public static NightState getInstance() {
        return nightState;
    }

    @Override
    public void doClock(Context context, int hour) { // 设置时间
        if (!(hour < 9 || hour > 17)) {
            context.changeState(DayState.getInstance());
        }
    }

    @Override
    public void doUse(Context context) { // 使用金库
        context.recordLog("紧急!晚上使用金库");
    }

    @Override
    public void doAlarm(Context context) { // 使用警报
        context.callSecurityCenter("按下警铃 [晚上]");
    }

    @Override
    public void doPhone(Context context) { // 使用电话
        context.callSecurityCenter("晚上电话录音");
    }
}

Context 接口

public interface Context {
    // 设置时间
    void setClock(int hour);

    // 改变状态
    void changeState(State state);

    // 调用 警报中心
    void callSecurityCenter(String msg);

    // 记录日志
    void recordLog(String msg);
}

SafeFrame

public class SafeFrame extends Frame implements ActionListener, Context {

    private TextField textClock = new TextField(60);

    private TextArea textScreen = new TextArea(10, 60);

    private Button buttonUse = new Button("使用金库");

    private Button buttonAlarm = new Button("使用警报");

    private Button buttonPhone = new Button("正常通话");

    private Button buttonExit = new Button("结束");


    // 当前状态
    private State state = DayState.getInstance();


    public SafeFrame(String title) throws HeadlessException {
        super(title);
        setBackground(Color.lightGray);
        setLayout(new BorderLayout());

        // 配置textClock
        add(textClock, BorderLayout.NORTH);
        textClock.setEditable(false);

        // 配置 textScreen
        add(textScreen, BorderLayout.CENTER);
        textScreen.setEditable(false);

        // 为界面添加按钮
        Panel panel = new Panel();
        panel.add(buttonUse);
        panel.add(buttonAlarm);
        panel.add(buttonPhone);
        panel.add(buttonExit);

        // 配置界面
        add(panel, BorderLayout.SOUTH);

        // 显示
        pack();
        show();

        // 设置监听器
        buttonUse.addActionListener(this);
        buttonAlarm.addActionListener(this);
        buttonPhone.addActionListener(this);
        buttonExit.addActionListener(this);
    }


    // 设置时间
    @Override
    public void setClock(int hour) {
        String clockString = "现在的时间是:";
        if (hour < 10) {
            clockString += "0" + hour + ":00";
        } else {
            clockString += hour + ":00";
        }
        System.out.println(clockString);

        textClock.setText(clockString);

        state.doClock(this, hour);
    }

    // 设置时间
    @Override
    public void changeState(State state) {
        System.out.println("从" + this.state + " 状态变成了 " + state + " 状态。");
        this.state = state;
    }

    // 联系警报中心
    @Override
    public void callSecurityCenter(String msg) {
        textScreen.append("Call!" + msg + "\n");
    }

    // 在警报中心留下记录
    @Override
    public void recordLog(String msg) {
        textScreen.append("record ..." + msg + "\n");
    }

    // 按钮被按下后该方法会被调用
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println(e.toString());
        if (e.getSource() == buttonUse) {  // 金库使用按钮
            state.doUse(this);
        } else if (e.getSource() == buttonAlarm) {  // 按下警铃按钮
            state.doAlarm(this);
        } else if (e.getSource() == buttonPhone) { // 使用电话
            state.doPhone(this);
        } else if (e.getSource() == buttonExit) { // 退出
            System.exit(0);
        } else {
            System.out.println("?");
        }
    }
}

功能分析

  1. 在state模式中,用类表示状态,并为每一种具体的状态都定义一个相应的类,可以替换复杂的 if-else;
  2. 使用Context 中的 一个state字段,就决定了系统的状态,不用定义过多的变量的值,不会存在自相矛盾的状态;
  3. 增加ConcreteState 角色是非常简单的,但是如果要增加“依赖于状态的处理”是非常困难的,因为要修改所有实现类中的方法;