zoukankan      html  css  js  c++  java
  • 设计模式(十九)State模式

      在面向对象编程中,是用类表示对象的。也就是说,程序的设计者需要考虑用类来表示什么东西。类对应的东西可能存在于真实世界中,也可能不存在于真实世界中。对于后者,可能有人看到代码后会感到吃惊:这些东西居然也可以是类啊。  

      在State模式中,用类来表示状态。用类来表示状态后,就能通过切换类方便地改变对象的状态,当需要增加新的状态时,如何修改代码这个问题也会很明确。

      示例程序的类图如上图所示。

    1 package bigjunoba.bjtu.state;
    2 
    3 public interface Context {
    4     public abstract void setClock(int hour);                // 设置时间
    5     public abstract void changeState(State state);          // 改变状态
    6     public abstract void callSecurityCenter(String msg);    // 联系警报中心
    7     public abstract void recordLog(String msg);             // 在警报中心留下记录
    8 }

      Context接口是负责管理状态和联系警报中心的接口。

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

      State接口是表示金库状态的接口。这些方法接收的参数Context是管理状态的接口。

     1 package bigjunoba.bjtu.state;
     2 
     3 public class DayState implements State {
     4     private static DayState singleton = new DayState();
     5 
     6     private DayState() { // 构造函数的可见性是private
     7     }
     8 
     9     public static State getInstance() { // 获取唯一实例
    10         return singleton;
    11     }
    12 
    13     public void doClock(Context context, int hour) { // 设置时间
    14         if (hour < 9 || 17 <= hour) {
    15             context.changeState(NightState.getInstance());
    16         }
    17     }
    18 
    19     public void doUse(Context context) { // 使用金库
    20         context.recordLog("使用金库(白天)");
    21     }
    22 
    23     public void doAlarm(Context context) { // 按下警铃
    24         context.callSecurityCenter("按下警铃(白天)");
    25     }
    26 
    27     public void doPhone(Context context) { // 正常通话
    28         context.callSecurityCenter("正常通话(白天)");
    29     }
    30 
    31     public String toString() { // 显示表示类的文字
    32         return "[白天]";
    33     }
    34 }

      DayState类表示白天的状态。对于每个表示状态的类,都只生成一个实例,因为如果每次发生状态改变时都生成一个实例的话,太浪费内存和时间了。因此,使用了Singleton模式。doClock方法是用于设置时间的方法。如果接收到的参数表示晚上的时间,就会切换到夜间状态,即发生状态变化,用程序表现就是获取夜晚状态的类的实例,然后通过changeState方法实现。

      

      1 package bigjunoba.bjtu.state;
      2 
      3 import java.awt.Frame;
      4 import java.awt.Label;
      5 import java.awt.Color;
      6 import java.awt.Button;
      7 import java.awt.TextField;
      8 import java.awt.TextArea;
      9 import java.awt.Panel;
     10 import java.awt.BorderLayout;
     11 import java.awt.event.ActionListener;
     12 
     13 import javax.swing.JButton;
     14 
     15 import java.awt.event.ActionEvent;
     16 
     17 public class SafeFrame extends Frame implements ActionListener, Context {
     18     private TextField textClock = new TextField(60); // 显示当前时间
     19     private TextArea textScreen = new TextArea(10, 60); // 显示警报中心的记录
     20     private JButton buttonUse = new JButton("使用金库"); // 金库使用按钮
     21     private JButton buttonAlarm = new JButton("按下警铃"); // 按下警铃按钮
     22     private JButton buttonPhone = new JButton("正常通话"); // 正常通话按钮
     23     private JButton buttonExit = new JButton("结束"); // 结束按钮
     24 
     25     private State state = DayState.getInstance(); // 当前的状态
     26 
     27     // 构造函数
     28     public SafeFrame(String title) {
     29         super(title);
     30         setBackground(Color.lightGray);
     31         setLayout(new BorderLayout());
     32         // 配置textClock
     33         add(textClock, BorderLayout.NORTH);
     34         textClock.setEditable(false);
     35         // 配置textScreen
     36         add(textScreen, BorderLayout.CENTER);
     37         textScreen.setEditable(false);
     38         // 为界面添加按钮
     39         Panel panel = new Panel();
     40         panel.add(buttonUse);
     41         panel.add(buttonAlarm);
     42         panel.add(buttonPhone);
     43         panel.add(buttonExit);
     44         // 配置界面
     45         add(panel, BorderLayout.SOUTH);
     46         // 显示
     47         pack();
     48         show();
     49         // 设置监听器
     50         buttonUse.addActionListener(this);
     51         buttonAlarm.addActionListener(this);
     52         buttonPhone.addActionListener(this);
     53         buttonExit.addActionListener(this);
     54     }
     55 
     56     // 按钮被按下后该方法会被调用
     57     public void actionPerformed(ActionEvent e) {
     58         System.out.println(e.toString());
     59         if (e.getSource() == buttonUse) { // 金库使用按钮
     60             state.doUse(this);
     61         } else if (e.getSource() == buttonAlarm) { // 按下警铃按钮
     62             state.doAlarm(this);
     63         } else if (e.getSource() == buttonPhone) { // 正常通话按钮
     64             state.doPhone(this);
     65         } else if (e.getSource() == buttonExit) { // 结束按钮
     66             System.exit(0);
     67         } else {
     68             System.out.println("?");
     69         }
     70     }
     71 
     72     // 设置时间
     73     public void setClock(int hour) {
     74         String clockstring = "现在时间是";
     75         if (hour < 10) {
     76             clockstring += "0" + hour + ":00";
     77         } else {
     78             clockstring += hour + ":00";
     79         }
     80         System.out.println(clockstring);
     81         textClock.setText(clockstring);
     82         state.doClock(this, hour);
     83     }
     84 
     85     // 改变状态
     86     public void changeState(State state) {
     87         System.out.println("从" + this.state + "状態变为了" + state + "状态。");
     88         this.state = state;
     89     }
     90 
     91     // 联系警报中心
     92     public void callSecurityCenter(String msg) {
     93         textScreen.append("call! " + msg + "
    ");
     94     }
     95 
     96     // 在警报中心留下记录
     97     public void recordLog(String msg) {
     98         textScreen.append("record ... " + msg + "
    ");
     99     }
    100 }

      SafeFrame类是使用GUI实现警报系统界面的类。这里要注意的是,没有先去判断时间是白天还是晚上,也没有判断金库的状态,如果按下按钮就立即执行对应的方法。changeState方法会调用白天状态和晚上状态两个类,当状态发生迁移时,实际改变状态的是this.state = state;这句话就是给代表状态的字段赋予表示当前状态的类的实例,就相当于进行了状态迁移。

      上图为状态改变前后doUse方法的调用流程。一开始调用DayState类的doUse方法,当changeState后,变为了调用NightState类的doUse方法。

     1 package bigjunoba.bjtu.state;
     2 
     3 public class Main {
     4     public static void main(String[] args) {
     5         SafeFrame frame = new SafeFrame("State Sample");
     6         while (true) {
     7             for (int hour = 0; hour < 24; hour++) {
     8                 frame.setClock(hour); // 设置时间
     9                 try {
    10                     Thread.sleep(1000);
    11                 } catch (InterruptedException e) {
    12                 }
    13             }
    14         }
    15     }
    16 }

      Main类作为测试类,很容易理解,就不做解释了。

      实际效果图如上。

      State模式的类图如上图。

      State模式扩展知识:

      1.分而治之。在大规模的复杂处理时,不能用一般的方法解决时,会先将多个问题分解为多个小问题来解决。

      2.在State接口中声明的所有方法都是“依赖于状态的处理”,都是“状态不同处理也不同”。实现起来总结为:1.定义接口,声明抽象方法。 2.定义多个类,实现具体方法。

      3.实例的多面性

  • 相关阅读:
    【转】 GetProcAddress()用法
    AutoCAD开发小记
    Visual Studio 2015正式版发布
    【VS2010]如何删除【附加依赖项】中“继承的值”?
    OpenCV入门指南
    Visual Studio 遇到了异常。这可能是由某个扩展导致的。
    VS2010在WIN7下安装报错“下列组件安装失败”如何解决
    获取系统日期时间的简单方法
    免费在线pdf互转工具
    应用层vc实现三种文件监视方法
  • 原文地址:https://www.cnblogs.com/BigJunOba/p/8806033.html
Copyright © 2011-2022 走看看