Author:Gorit
Date:2021年11月25日
Refer:《图解设计模式》

19.1 State 模式

在 State 模式,我们将会用 “类”来表示状态。

现实世界中,我们会考虑各种东西的“状态”,但是不会讲“状态”当做东西看待

本章我们将会用 类 来表示状态,通过切换类来方便的改变对象的状态。当腰新增代码状态时,如何修改代码这个问题会变得明确。

State 模式中:

  1. 使用方法判断状态
  2. 用类来表示状态

19.2 示例程序

类名 说明
State 表示金库状态的接口
DayState 表示“白天”状态的类,实现了 State 接口
NightState 表示“晚上”状态的类,实现了State 接口
Context 表示管理金库状态,并与警报中心联系的接口
SafeFrame 实现 Context 接口, 在它内部持有 按钮 和 画面显示 UI 信息
Main 测试程序行为的类

State

  1. package State;
  2. /**
  3. * @Author Gorit
  4. * @Date 2021/11/26
  5. * 设置时间
  6. * 使用金库
  7. * 按下警铃
  8. * 正常通话
  9. **/
  10. public interface State {
  11. public abstract void doClock(Context context, int hour); // 设置时间
  12. public abstract void doUse(Context context); // 使用金库
  13. public abstract void doAlram(Context context); // 按下警铃
  14. public abstract void doPhone(Context context); // 正常通话
  15. }

DayState

  1. package State;
  2. /**
  3. * @Author Gorit
  4. * @Date 2021/11/26
  5. **/
  6. public class DayState implements State{
  7. private static DayState singleton = new DayState();
  8. private DayState() {}
  9. // 单例模式获取唯一实例
  10. public static State getInstance() {
  11. return singleton;
  12. }
  13. public void doClock(Context context, int hour) {
  14. if (hour < 9 || hour >= 17) {
  15. context.changeState(NightState.getInstance());
  16. }
  17. }
  18. public void doUse(Context context) {
  19. context.recordLog("使用金库(白天)");
  20. }
  21. public void doAlram(Context context) {
  22. context.callSecurityCenter("按下警铃(白天)");
  23. }
  24. public void doPhone(Context context) {
  25. context.callSecurityCenter("正常通话(白天)");
  26. }
  27. public String toString() {
  28. return "[白天]";
  29. }
  30. }

NightState

  1. package State;
  2. /**
  3. * @Author Gorit
  4. * @Date 2021/11/26
  5. **/
  6. public class NightState implements State {
  7. private static NightState singleton = new NightState();
  8. private NightState() {}
  9. // 单例模式获取唯一实例
  10. public static State getInstance() {
  11. return singleton;
  12. }
  13. public void doClock(Context context, int hour) {
  14. if (hour <= 9 && hour < 17) {
  15. context.changeState(DayState.getInstance());
  16. }
  17. }
  18. public void doUse(Context context) {
  19. context.callSecurityCenter("紧急!晚上使用金库!");
  20. }
  21. public void doAlram(Context context) {
  22. context.callSecurityCenter("按下警铃(晚上)");
  23. }
  24. public void doPhone(Context context) {
  25. context.recordLog("晚上通话录音");
  26. }
  27. public String toString() {
  28. return "[夜晚]";
  29. }
  30. }

Context

  1. package State;
  2. /**
  3. * @Author Gorit
  4. * @Date 2021/11/26
  5. **/
  6. public interface Context {
  7. public abstract void setClock(int hour); // 设置时间
  8. public abstract void changeState(State state); // 改变状态
  9. public abstract void callSecurityCenter(String msg); // 联系警报中心
  10. public abstract void recordLog(String msg);
  11. }

SafeFrame

  1. package State;
  2. import java.awt.*;
  3. import java.awt.event.ActionEvent;
  4. import java.awt.event.ActionListener;
  5. /**
  6. * @Author Gorit
  7. * @Date 2021/11/26
  8. * 设置背景色
  9. * 设置布局管理器
  10. * 设置控件
  11. * 设置监听器(Listener)
  12. **/
  13. public class SafeFrame extends Frame implements ActionListener, Context {
  14. private TextField textClock = new TextField(60); // 显示当前时间
  15. private TextArea textScreen = new TextArea(10, 60); // 显示警报中心记录
  16. private Button buttonUse = new Button("使用金库");
  17. private Button buttonAlert = new Button("按下警铃");
  18. private Button buttonPhone = new Button("正常通话");
  19. private Button buttonExit = new Button("结束");
  20. private State state = DayState.getInstance(); // 当前状态
  21. public SafeFrame(String title) throws HeadlessException {
  22. super(title);
  23. setBackground(Color.lightGray);
  24. setLayout(new BorderLayout());
  25. // 配置 textClock
  26. add(textClock, BorderLayout.NORTH);
  27. textClock.setEditable(false);
  28. // 配置 text Screen
  29. add(textScreen, BorderLayout.CENTER);
  30. // 为界面添加按钮
  31. Panel panel = new Panel();
  32. panel.add(buttonUse);
  33. panel.add(buttonAlert);
  34. panel.add(buttonPhone);
  35. panel.add(buttonExit);
  36. // 配置界面
  37. add(panel, BorderLayout.SOUTH);
  38. // 显示
  39. pack();
  40. setVisible(true);
  41. // 设置监听器
  42. buttonUse.addActionListener(this);
  43. buttonAlert.addActionListener(this);
  44. buttonPhone.addActionListener(this);
  45. buttonExit.addActionListener(this);
  46. }
  47. public void setClock(int hour) {
  48. String clockString = "现在时间是:";
  49. if (hour < 10) {
  50. clockString += "0" + hour + ":00";
  51. } else {
  52. clockString += hour + ":00";
  53. }
  54. System.out.println(clockString);
  55. textClock.setText(clockString);
  56. state.doClock(this, hour);
  57. }
  58. // 联系警报中心
  59. public void callSecurityCenter(String msg) {
  60. textScreen.append("call!!!" + msg + "\n");
  61. }
  62. // 警告中心留下记录
  63. public void recordLog(String msg) {
  64. textScreen.append("record...." + msg + "\n");
  65. }
  66. // 改变状态
  67. public void changeState(State state) {
  68. System.out.println("从" + this.state + "状态变为" + state + "状态");
  69. this.state = state;
  70. }
  71. public void actionPerformed(ActionEvent e) {
  72. System.out.println(e.toString());
  73. if (e.getSource() == buttonUse) {
  74. state.doUse(this); // 金库使用按钮
  75. } else if (e.getSource() == buttonAlert) {
  76. state.doAlram(this); // 按下报警按钮
  77. } else if (e.getSource() == buttonPhone) {
  78. state.doPhone(this); // 按下打电话
  79. } else if (e.getSource() == buttonExit) {
  80. System.exit(0); // 退出
  81. } else {
  82. System.out.println("?");
  83. }
  84. }
  85. }

Main

  1. package State;
  2. /**
  3. * @Author Gorit
  4. * @Date 2021/11/26
  5. **/
  6. public class Main {
  7. public static void main(String[] args) {
  8. SafeFrame frame = new SafeFrame("State Simple");
  9. while (true) {
  10. for (int hour = 0; hour < 24; hour++) {
  11. frame.setClock(hour);
  12. try {
  13. Thread.sleep(1000);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. }
  19. }
  20. }

运行截图
image.png

19.3 State 中登场的角色

一、State 状态

State 角色表示状态,定义了根据不同状态进行不同处理的接口(API)。

该接口是那些 处理内容依赖于状态的方法的集合

二、ConcreateState(具体状态)

ConcreateState 角色表示具体的状态,实现了 State 的接口。示例程序中由 DayState 和 NightState 类扮演此角色

三、Context(状况,前后关系,上下文)

Context 角色持有表示当前状态的 ConcreateState 角色。此外,它还定义了外部调用者使用 State 模式的接口(API)。

示例程序由 Context 和 SafeFrame 表示当前角色状态

本案例中:

  1. Context 接口定义了供外部调用者使用 State 模式的接口
  2. SafeFrame 类则持有表示当前状态的 ConcreateState 角色。

    19.4 相关设计模式

    一、Singleton 模式

    Singleton 模式常常出现在 ConcreateState 角色中。生成唯一实例来表示当前状态

二、Flyweight 模式

在表示状态的类中没有定义任何实例字段。

因此,有时我们可以用 Flyweight 模式在多个 Context 角色之间共享 ConcreateState 角色。