设计模式(十九)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 + "\n");
 94     }
 95 
 96     // 在警报中心留下记录
 97     public void recordLog(String msg) {
 98         textScreen.append("record ... " + msg + "\n");
 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.实例的多面性

 

posted @ 2018-04-12 10:45  BigJunOba  阅读(360)  评论(0编辑  收藏  举报