状态模式(用类表示状态)
用处
以类表示状态后,可以通过切换类来方便地切换对象的状态,当需要增加新的状态时,如何修改代码也很明确,可以省去许多if,else的状态判断。
角色
- State (状态)
该角色表示状态,定义了根据不同状态进行不同处理的接口(API)。该接口(API)是那些处理内容依赖于状态的方法的集合。 - ConcreteState(具体状态)
该角色表示各个具体的状态,它实现了State接口。 - Context(上下文)
该角色持有表示当前状态的ConcreteState角色。此外,它还定义了供外部调用者使用State模式的接口(API)。
类图
注:图中的State少了个MethodD
由类图可以看出
- Context类提供了一个对外部调用的接口,通过State接口聚合了ConcreteState类,其中ConcreteState类可以任意切换(多态)。
- Context类中的方法(requestX,requestY,requestZ)中调用State接口的抽象方法(methodA,methodB,methodC),当聚合的ConcreteState对象切换时,调用的方法也不同,以此来实现在不同状态下实现不同的功能。
举例
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){
}
}
}
}
}
/**
* 需求:
* -有一个金库
* -金库与警报中心相连
* -金库里有警铃和正常通话用的电话
* -金库里由时钟,监视着现在的时间
* --------------------------
* -白天的时间范围是9:00——16:59,晚上的时间范围是:17:00——23:59和0:00——8:59
* --------------------------
* 金库只能在白天使用
* 白天使用金库的话,会在警报中心留下记录
* 晚上使用金库的话,会向报警中心发送紧急事态通知
* --------------------------
* 任何时候都可以使用警铃
* 使用警铃的话,会向警报中心发送紧急事态通知
* --------------------------
* 任何时候都可以使用电话(但晚上只有留言电话
* 白天使用电话的话,会呼叫警报中心
* 晚上使用电话的话,会呼叫报警中心的留言电话
* **/
//State角色
interface State{
void doClock(Context context, int hour);
void doUse(Context context);
void doAlarm(Context context);
void doPhone(Context context);
}
//ConcreteState角色
class DayState implements State{
private static DayState singleton = new DayState();
private DayState(){}
public static State getInstance(){
return singleton;
}
public void doClock(Context context,int hour){
if(hour < 9 || hour >= 17){
//管理状态迁移
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 "[白天]";
}
}
//ConcreteState角色
class NightState implements State{
private static NightState singleton = new NightState();
private NightState(){}
public static State getInstance(){
return singleton;
}
public void doClock(Context context,int hour){
if(hour >= 9 && hour < 17){
context.changeState(DayState .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 "[晚上]";
}
}
//Context角色的抽象
interface Context{
void setClock(int hour);
void changeState(State state);
void callSecurityCenter(String msg);
void recordLog(String msg);
}
//Context角色
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("use");
private Button buttonAlarm = new Button("alarm");
private Button buttonPhone = new Button("phone");
private Button buttonExit = new Button("exit");
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("cell"+msg+"\n");
}
//在警报中心留下记录
public void recordLog(String msg){
textScreen.append("record..."+msg+"\n");
}
}
总结
- 符合开闭原则
- 分而治之的思路,将每个状态的处理分散在状态类上,省去了分支条件判断,当类非常多的时候有优势。
- 举例中管理状态迁移(ChangeState)的是ConcreteState角色,我们也可以在Context角色中管理状态迁移。