设计模式之状态模式(state模式)状态模式的具体实例状态模式的分析

面向对象编程中,类用来表示对象,一般情况下,我们需要考虑用类来表示什么具体的东西。类对应的东西可能存在于真实世界中,也可能不存在于真实世界中。 状态模式所表示的类,一般就不存在真实世界的某个东西,因为状态模式中的类是用来表示状态的。状态一般都是抽象的,所以往往没有具体对应于真实世界的对象。 我们用类来表示状态,那么不同的状态就用不同的类来表示,我们只要通过切换不同的类就可以切换不同的状态。

状态模式的具体实例

我们考虑设计一个金库警报系统,这个系统会根据白天晚上做出不同的响应。

有一个金库 金库与警报中心相连 金库里有警铃和电话 金库里有时钟

金库只能在白天使用 白天使用金库,会在警报中心留下记录 晚上使用金库,会向警报中心发送紧急事态通知

警铃白天晚上都能用 使用警铃,会向警报中心发送紧急事态通知

电话都可以使用 白天使用电话,会呼叫警报中心 晚上使用电话,会呼叫警报中心的留言电话

基本就是以上的需求逻辑。

如果我们不使用状态模式 那就是大概伪码如下:

使用金库调用的方法() {
  if(白天) {
} else if(晚上) {
}
}

正常通话时() {
if(白天) {}
else if(晚上) {
}
}

显然这样可以实现,也并没有什么错误。

但是状态模式确实从不同的角度来考虑问题。

状态模式会发现,这些不同的行为,主要依赖于两个状态,就是白天和晚上。所以状态模式会抽象出这两种状态,每个状态就会有自己的行为实现,比如白天这个状态会实现自己的使用金库的方法,通话的方法,晚上的类也会实现自己的行为逻辑,最后我们只要取得状态对象的委托调用他们的方法就行了,不管他们具体是怎么实现的。 我们看一下使用状态模式的伪码:

白天的状态类 {
      使用金库的方法
      使用警铃的方法
     通话的方法
}


晚上的状态类 {
      使用金库的方法
      使用警铃的方法
     通话的方法
}

我们看到普通方法和状态模式的区别就是状态模式中,定义了状态类,就不需要if语句来判断了。所以当我们遇到很多个ifelse语句的时候,往往可以考虑状态模式,阿里最新的java开发手册里面就有一条相关的推荐

image.png

下面我们就来具体实现代码

定义状态类的接口:

package State;

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);              // 正常通话
}

实现两个具体的白天和晚上的状态类

package State;

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 "[白天]";
    }
}
package State;

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 "[晚上]";
    }
}

Context类是负责管理状态和联系警报中心的接口,它定义了基本的警报中心的行为,

package State;

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);             // 在警报中心留下记录
}

safeframe类实现了context接口

package State;

import java.awt.Frame;
import java.awt.Label;
import java.awt.Color;
import java.awt.Button;
import java.awt.TextField;
import java.awt.TextArea;
import java.awt.Panel;
import java.awt.BorderLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

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) {
        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 + "\n");
    }
    // 在警报中心留下记录
    public void recordLog(String msg) {
        textScreen.append("record ... " + msg + "\n");
    }
}

main类启动

package State;

public class Main {
    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) {
                }
            }
        }
    }
}

image.png

状态模式的分析

状态模式的角色:

  • state状态 表示状态,定义了根据不同状态进行不同处理的接口,该接口是那些处理内容依赖于状态的方法集合,对应实例的state类
  • 具体的状态 实现了state接口,对应daystate和nightstate
  • context context持有当前状态的具体状态的实例,此外,他还定义了供外部调用者使用的状态模式的接口。

状态模式的类图:

image.png

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java呓语

适配器模式(转化匹配,复用功能)

如果您是第一次阅读我的设计模式系列文章,建议先阅读设计模式开篇,希望能得到您宝贵的建议。

924
来自专栏web前端教室

web前面装逼指南

开篇语: 生有涯而装逼无涯 ---我曰(yue)的 web前端现在也是百花齐放了, 野路子出来的, 科班出来的, 搬砖转过来的, 失足掉进来的...

1876
来自专栏向治洪

Kotlin和anko融合进行Android开发

kotlin是一门基于jvm的编程语言,最近进行了关于kotlin和 anko的研究。并且结合现在的APP设计模式,设想了初步的开发方式。并且准备应用在新的项目...

2996
来自专栏向治洪

Kotlin和anko融合进行Android开发

kotlin是一门基于jvm的编程语言,最近进行了关于kotlin和 anko的研究。并且结合现在的APP设计模式,设想了初步的开发方式。并且准备应用在新的项目...

1966
来自专栏牛客网

百度、今日头条、新东方、滴滴社招安卓面经一、百度二、新东方(是新东方教育,不是新东方厨师呀,新东方厨师的广告跟挖掘机一样,打得非常响亮)三、头条四、滴滴

1754
来自专栏帅小子的日常

购物车的原理以及实现

7223
来自专栏听Allen瞎扯淡

玩花招的PowerMock

当我们面对一个遗留系统时,常见的问题是没有测试。正如Michael Feathers在Working Effectively with Legacy Code一...

1182
来自专栏流媒体

MediaCodec进行AAC编解码(AudioRecord采集录音)

最近工作比较忙,很久没有更新这个系列的文章。我们先回顾一下上一篇MediaCodec进行AAC编解码(文件格式转换)的内容,里面介绍了MediaExtracto...

2134
来自专栏智能大石头

MF干活——C#数码管与跑马灯之舞(视频)

    通过《C#点灯神话》,我们了解到单片机最基础的输入输出功能,也就是所谓的GPIO。     这一次,我们来点更多的“灯”——数码管,并且还是四位数码管。...

2036
来自专栏IMWeb前端团队

那些年我们踩过的坑

事件背景 有一天leader给程序员cover分配了一个需求,cover一看,需求很简单嘛,就是在页面异步拉取数据展示就OK了,于是就和cgi同事阿翔对接了一下...

24610

扫码关注云+社区

领取腾讯云代金券