使用类表示状态-State模式

595 阅读2分钟

1、引入

在不同环境下,程序的处理往往是不同的。

一种解决方式是在程序里做对不同状态的判断,分别处理;而这里要展示的另一种解决方式是视状态为对象,在不同环境下通过对状态的切换来实现不同功能。即State模式。

2、示例

模拟一个金库警报系统,在白天和夜晚对系统的操作(使用、报警、通话)会有不同反应。

2.1、状态类

2.1.1、状态的抽象接口

public interface State {
    public abstract void doClock(Context context, int hour);
    public abstract void doUse(Context context);
    public abstract void doAlarm(Context context);
    public abstract void doPhone(Context context);
}

2.1.2、状态一:白天

public class DayState implements State{
    private static DayState singleton=new DayState();

    private DayState() {
    }

    public static State getInstance() {
        return singleton;
    }

    @Override
    public void doClock(Context context, int hour) {
        if (hour < 9 || 17 <= hour) {
            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("正常通话(白天)");
    }

    public String toString() {
        return "[白天]";
    }
}

2.1.3、状态二:夜晚

public class NightState implements State{
    private static NightState singleton=new NightState();

    private NightState() {
    }

    public static NightState getInstance() {
        return singleton;
    }

    @Override
    public void doClock(Context context, int hour) {
        if (9 <= hour && hour < 17) {
            context.changeState(DayState.getInstance());
        }
    }

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

    @Override
    public void doAlarm(Context context) {
        context.callSecurityCenter("按下警铃(晚上)");
    }

    @Override
    public void doPhone(Context context) {
        context.recordLog("晚上的电话录音");
    }

    public String toString() {
        return "[晚上]";
    }
}

2.2、金库警报系统

2.2.1、警报行为的抽象接口

public interface Context {
    public abstract void setClock(int hour);
    public abstract void changeState(State state);
    public abstract void callSecurityCenter(String msg);
    public abstract void recordLog(String msg);
}

2.2.2、系统实现

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("Use");
    private Button buttonAlarm = new Button("Alarm");
    private Button buttonPhone = new Button("Call");
    private Button buttonExit = new Button("Exit");
    private State state = DayState.getInstance();
    //可以不必在意前端页面是怎么实现的
    public SafeFrame(String title) throws HeadlessException {
        super(title);
        setBackground(Color.lightGray);
        setLayout(new BorderLayout());
        add(textClock, BorderLayout.NORTH);
        textClock.setEditable(false);
        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("recode ... " + 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("?");
        }
    }
}

2.3、测试

public class Main {
    public static void main(String[] args) {
        SafeFrame state_simple = new SafeFrame("State Simple");
        while (true) {
            for (int hour = 0; hour < 24; hour++) {
                state_simple.setClock(hour);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果:

image.png

3、tips

  • 之所以要对所有的状态类作统一接口,在规范的同时也易于系统对新的状态进行扩展。
  • 在状态少的时候使用判断直接处理可能会较为方便,但是当状态多或是状态增加时,State模式大大提升了代码的简洁性以及拓展性,实现了解耦。
  • 有时候可以用enum类来表示状态。