在面向对象编程中,是用类表示对象的。在State模式中,用类来表示状态。State的意思就是“状态”。在现实生活中,会考虑各种东西的状态,但是几乎不会将状态当做“东西”看待。
用类来表示状态以后,就能通过切换类来方便地改变对象的状态。当需要增加新的状态时,如何修改代码也会很明确。
示例程序:
警戒状态每小时都会改变一次的警报系统。
- 有一个金库 - 金库与警报中心相连 - 金库里有警铃和正常通话用的电话 - 金库里有时钟,监视着现在的时间 |
|---|
- 白天的时间范畴是9:00-16.59,晚上的时间范畴是17:00-23:59和0:00-8.59 |
- 金库只能在白天使用 - 白天使用金库的话,会在警报中心留下记录 - 晚上使用金库的话,会向警报中心发送紧急事态通知 |
- 任何时候都可以使用警铃 - 使用警铃的话,会向警报中心发送紧急事态通知 |
- 任何时候都可以使用电话(但晚上只有留言电话) - 白天使用电话的话,会呼叫警报中心 - 晚上用电话的话,会呼叫警报中心的留言电话 |
类和接口的一览表:
| 名字 | 说明 |
|---|---|
| State | 表示金库状态的接口 |
| DayState | 表示“白天”的类,实现了State接口 |
| NightState | 表示“晚上”的类,实现了State接口 |
| Context | 表示管理金库状态,并与警报中心联系的接口 |
| SafeFrame | 实现了Context接口。在它内部持有按钮和画面显示等UI信息 |
| Main | 测试程序行为的类 |
State接口:
State接口是表示金库状态的接口,在State接口中定义了以下事件对应的接口。
- 设置时间
- 使用金库
- 按下警铃
正常通话 ```java 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); // 正常通话 }
<a name="R1SmR"></a>#### DayState类:DayState类表示白天的状态。<br />对于每个表示状态的类,都只会生成一个实例对象。因为如果每次发生状态改变时都生成一个实例的话,太浪费内存和时间了。为此,使用了单例模式。```javapublic class DayState implements State {private static DayState singleton = new DayState();private DayState() {}public static State getInstance() {return singleton;}@Overridepublic void doClock(Context context, int hour) {if (hour < 9 || 17 <= hour) {context.changeState(NightState.getInstance());}}@Overridepublic void doUse(Context context) {context.recordLong("使用金库(白天)");}@Overridepublic void doAlarm(Context context) {context.recordLong("按下警铃(白天)");}@Overridepublic void doPhone(Context context) {context.callSecurity("正常通话(白天)");}public String toString() {return "[白天]";}}
NightState类:
public class NightState implements State {private static NightState instance = new NightState();private NightState() {}public static State getInstance() {return instance;}@Overridepublic void doClock(Context context, int hour) {if (9 <= hour && hour < 17) {context.changeState(DayState.getInstance());}}@Overridepublic void doUse(Context context) {context.callSecurity("紧急:晚上使用金库!");}@Overridepublic void doAlarm(Context context) {context.callSecurity("按下警铃(晚上)");}@Overridepublic void doPhone(Context context) {context.recordLong("晚上的通话录音");}public String toString() {return "[晚上]";}}
Context接口:
负责管理状态和联系警报中心的接口。
public interface Context {public abstract void setClock(int hour); // 设置时间public abstract void changeState(State state); // 改变状态public abstract void callSecurity(String msg); // 联系警报中心public abstract void recordLong(String msg); // 在警报中心留下记录}
SafeFrame:
safeFrame的构造函数做了以下处理:
- 设置背景色;
- 设置布局管理器;
- 设置控件;
- 设置监听器。
通过调用各个按钮的addActionListener方法来设置监听器。程序结构类似于观察者模式。
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());// 配置textClockadd(textClock, BorderLayout.NORTH);textClock.setEditable(false);// 配置textScreenadd(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);}@Overridepublic 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);}@Overridepublic void callSecurity(String msg) {textScreen.append("call!" + msg + "\n");}@Overridepublic void recordLong(String msg) {textScreen.append("record ..." + msg + "\n");}// 修改状态@Overridepublic void changeState(State state) {System.out.println("从" + this.state + "状态变为了" + state + "状态。");this.state = state;}// 按键被按下后该方法会被调用@Overridepublic 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("?");}}}
State模式中的登场角色:
State(状态)
State角色表示状态,定义了根据不同状态进行不同处理的接口。该接口是那些处理内容依赖于状态的方法的集合。
ConcreteState(具体状态):
ConcreteState角色表示各个具体的状态,它实现了State接口。
Context(状态、前后关系、上下文):
Context角色持有表示当前状态的ConcreteState角色。此外,它还定义了供外部调用者使用State模式的接口。
拓展思路:
分而治之:
在编程时,这种思想非常适用于大规模的复杂处理。当遇到庞大而复杂的问题,不能用一般的方法解决时,可以先将问题分解为多个小问题。如果还是不能解决,就将其拆分开更小的问题,直至可以解决它们为止。
在State模式中,用类来表示状态,并为每一种具体的状态都定义一个相应的类。当状态非常多的时候,State模式的优势就会非常明显了。
相关的设计模式:
Singleton模式:
Singleton会经常出现在ConcreteState角色中。
Flyweight模式:
在表示状态的类中并没有定义任何实例字段。有时可以使用Flyweight模式在多个Context角色之间共享ConcreteState角色。
