• 《图解设计模式》读书笔记8-3 STATE模式


    State模式

    State模式即状态模式,用类表示状态,通过切换类来方便地改变对象的状态。

    示例程序

    实现的功能

    有一个金库和一个警报系统,金库里面有三个装置,根据时间变化,触发它们会产生不同的动作。

    时间范围

    白天:早上9点到下午5点

    晚上:下午5点到第二天上午9点

    三个装置

    使用金库按钮:在白天使用,会在警报中心留下使用痕迹;在晚上使用,会触发警报中心的警铃。

    警铃:在任何时候使用都会触发警报中心的警铃。

    电话:在白天使用会拨通警报中心的电话;在晚上使用会给警报中心的电话留言。

    不使用&使用状态模式对比

    不使用状态模式

    警报系统的类{
        使用金库按钮方法(){
            if (白天) {
                在警报中心留下使用记录
            } else if (晚上) {
                触发警铃
            }
        }
        警铃方法(){
            报告紧急状态
        }
        电话方法(){
            if (白天) {
                拨通警报中心电话
            } else if (晚上) {
                给警报中心留言
            }
        }
    }
    

    使用状态模式

    表示白天的状态类 {
        使用金库按钮方法(){
            在警报中心留下使用记录
        }
        警铃方法(){
            报告紧急状态
        }
        电话方法(){
            拨通警报中心电话
        }
    }
    表示晚上的状态类 {
        使用金库按钮方法(){
            触发警铃
        }
        警铃方法(){
            报告紧急状态
        }
        电话方法(){
            给警报中心留言
        }
    }
    

    在state模式中,用类表示状态,不需要用if else 来判断当前状态了。

    示例程序的类图

    代码

    State接口

    public interface State {
        // 设置时间
        void doClock(Context context, int hour);
        // 使用金库按钮
        void doUse(Context context);
        // 按下警铃
        void doAlarm(Context context);
        // 正常通话
        void doPhone(Context context);
    }
    

    Context接口

    负责管理状态和做出响应处理的接口

    public interface Context {
        // 设置时间
        void setClock(int hour);
        // 改变状态
        void changeState(State state);
        // 联系警报中心
        void callSecurityCenter(String msg);
        // 在警报中心留下记录
        void recordLog(String msg);
    }
    

    DayState类,表示白天的状态

    doClock方法中,Context根据当前时间切换状态,利用单例模式避免浪费内存

    public class DayState implements State {
        private static DayState singleton = new DayState();
        private DayState() {                                // 构造函数的可见性是private
        }
        public static State getInstance() {                 // 获取唯一实例
            return singleton;
        }
    
        public void doClock(Context context, int hour) {    // 设置时间
            if (hour < 9 || 17 <= hour) {
                context.changeState(NightState.getInstance());
            }
        }
        public void doUse(Context context) {                // 使用金库
            context.recordLog("使用金库(白天)");
        }
        public void doAlarm(Context context) {              // 按下警铃
            context.callSecurityCenter("按下警铃(白天)");
        }
        public void doPhone(Context context) {              // 正常通话
            context.callSecurityCenter("正常通话(白天)");
        }
        public String toString() {                          // 显示表示类的文字
            return "[白天]";
        }
    }
    

    NightState类,表示晚上的状态

    public class NightState implements State {
        private static NightState singleton = new NightState();
        private NightState() {                              // 构造函数的可见性是private
        }
        public static State getInstance() {                 // 获取唯一实例
            return singleton;
        }
        public void doClock(Context context, int hour) {    // 设置时间
            if (9 <= hour && hour < 17) {
                context.changeState(DayState.getInstance());
            }
        }
        public void doUse(Context context) {                // 使用金库
            context.callSecurityCenter("紧急:晚上使用金库!");
        }
        public void doAlarm(Context context) {              // 按下警铃
            context.callSecurityCenter("按下警铃(晚上)");
        }
        public void doPhone(Context context) {              // 正常通话
            context.recordLog("晚上的通话录音");
        }
        public String toString() {                          // 显示表示类的文字
            return "[晚上]";
        }
    }
    

    SafeFrame类,实现Context接口的具体类

    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("phone");    // 正常通话按钮
        private Button buttonExit = new Button("cancel");         // 结束按钮
    
        private State state = DayState.getInstance();           // 当前的状态
    
        // 构造函数
        public SafeFrame(String title) {
            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);
        }
        // 按钮被按下后该方法会被调用
        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("?");
            }
        }
        // 设置时间
        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);
        }
        // 改变状态
        public void changeState(State state) {
            System.out.println("从" + this.state + "状态变为了" + state + "状态。");
            this.state = state;
        }
        // 联系警报中心
        public void callSecurityCenter(String msg) {
            textScreen.append("call! " + msg + "
    ");
        }
        // 在警报中心留下记录
        public void recordLog(String msg) {
            textScreen.append("record ... " + msg + "
    ");
        }
    }
    
    

    Main

    每隔1秒钟刷新一次时间并调用SafeFrame的setClock方法,如果当前时间所处区间发生了变化,则SafeFrame里面的state实例会被重新设置。因为点击按钮触发的方法是state的方法,所以点击按钮产生的效果会随着时间改变而改变。

    	public static void main(String[] args) {
            SafeFrame frame = new SafeFrame("State Sample");
            while (true) {
                for (int hour = 0; hour < 24; hour++) {
                    frame.setClock(hour);   // 设置时间
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                }
            }
        }
    

    结果: 按下按钮后,根据时间不同会有不同的效果。

    角色和类图

    角色

    • State(状态)

    State角色表示状态,是具体状态抽象出来的接口。接口中的方法是:处理内容依赖于状态的API的集合。本例中,由State接口扮演此角色。

    • ConcreteState(具体状态)

    表示具体的状态,本例中由DayStateNightState扮演此角色。

    • Context(上下文)

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

    在本例中Context接口和SafeFrame类分担了这个角色。Context提供供外部调用者使用的State模式的接口,SafeFrame则持有ConcreteState角色。

    类图

    拓展思路

    分而治之

    分治法是解决复杂问题的利器,State模式就体现了分治法。如果状态非常多,则需要相当多的if else判断,这些条件和处理方法散落在系统各处,很难查找、理解和修改。状态模式将程序的状态部分从业务代码中剥离,把每个状态聚合成为一个类,大大降低了查找、理解和修改的难度。

    依赖于状态的处理

    SafeFrame类中,setClock方法和actionPerformed方法都依赖于状态的处理,即根据状态执行的方法,都把方法委托给了状态类来处理。

    如何实现依赖于状态的处理?

    1.定义状态(State)接口,声明抽象方法。 2.定义多个具体的状态(ConcreteState)类,实现抽象方法。

    这样,Context类内部只要保留有State的引用,调用抽象方法。此时方法就会根据不同的状态执行不同的结果了。

    谁来管理状态迁移

    在本例中,利用ConcreteState类来管理状态迁移,这样做有好有坏,好处是把状态迁移条件集中到了一个类里面,比如我们想知道什么时候由白天变成黑夜,只需要阅读白天的状态类代码即可。坏处是各个状态类的耦合度增加了。

    也可以用Context类,结合Mediator模式管理各个状态之间的迁移,这样使得ConcreteState类直接更加独立。

    也可以不使用State模式,使用状态迁移表来设计程序。

    易于增加新状态

    如果要增加新状态,只需要添加一个实现State接口的ConcreteState类即可,同时也要注意修改状态迁移代码。总的来说,比一个个修改if else强得多。

    如果要增加新的状态处理方法则相对麻烦,需要在State接口中添加方法并在每个ConcreteState类中添加对应的实现方法,好在不会忘记:如果有的ConcreteState没有实现处理方法,编译都通不过。

    实例的多面性

    请注意SafeFrame中的两个语句

    public SafeFrame(String title) {
        ...
        buttonUse.addActionListener(this);
        ...
    }
    
    public void actionPerformed(ActionEvent e) {
       ...
       if (e.getSource() == buttonUse) {           
          state.doUse(this);
       }
       ...
    }
    

    两个this,第一个表示ActionListener,第二个表示Context,因为SafeFrame实现了ActionListenerContext两个接口,所以它既是前者,也是后者。注意这里体现出来的实例的多面性

  • 相关阅读:
    如何用VSCode手动编译Ace Editor
    libuv源码分析
    二叉平衡查找树---红黑树
    tcp滑动窗口与拥塞控制
    ceph架构剖析
    腾讯面试总结
    协程的实现原理
    dhcp协议交互报文
    libuv源码分析前言
    Protobuf使用规范分享
  • 原文地址:https://www.cnblogs.com/qianbixin/p/11197254.html
Copyright © 2020-2023  润新知