欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

设计模式:State模式

程序员文章站 2022-05-30 23:12:38
...

State模式——用类表示状态

在面向对象编程中,是用类表示对象的。也就是说,一个类对应着一个东西。这个东西可以存在于现实世界中,也可以不存在在现实世界中。在State模式中,我们使用类来表示状态。以类来表示状态后,我们就能通过切换类来方便地改变对象的状态。当需要增加新的状态时,如何修改代码这个问题也会变得非常简单。

下面的示例程序是一个金库报警系统。

我们只是写出接口做一定地模拟,假设程序中的1秒对应现实中的一小时来进行测试。

这里需要实现的功能如下:

  • 有一个金库
  • 金库与报警中心相连
  • 金库里有警铃和正常通话用的电话
  • 金库里有时钟,监视着当前的时间

  • 白天的时间范围是9:00~16::49,晚上的时间范围是17:00~23:59和0:00~8:59

  • 金库只能在白天使用

  • 白天使用金库的话,会在警报中心留下记录
  • 晚上使用金库的话,会向警报中心发送紧急事态通知

  • 任何时候都可以使用警铃

  • 使用警铃的话,会向紧急中心发送紧急事态通知

  • 任何时候都可以使用电话(但是晚上只有留言电话)

  • 白天使用电话的时候,会呼叫警报中心
  • 晚上使用电话的时候,会呼叫警报中心的留言电话

不使用State模式时的伪代码:

警报系统的类{
    使用金库时被调用的方法(){
        if(白天){
            向警报中心报告使用记录
        }else if(晚上){
            向警报中心报告紧急事态
        }
    }

    警铃响起时被调用的方法(){
        向警报中心报告紧急事态
    }

    正常通话时被调用的方法(){
        if(白天){
            呼叫警报中心
        }else if(晚上){
            呼叫警报中心的留言电话
        }
    }
}

使用State模式时的伪代码:

表示白天状态的类{
    使用金库时被调用的方法(){
        向警报中心报告使用记录
    }

    警铃响起时被调用的方法(){
        向警报中心报告紧急事态
    }

    正常通话时被调用的方法(){
        呼叫警报中心
    }
}

表示晚上状态的类{
    使用金库时被调用的方法(){
        向警报中心报告紧急事态
    }

    警铃响起时被调用的方法(){
        向警报中心报告紧急事态
    }

    正常通话时被调用的方法(){
        呼叫警报中心的留言电话
    }
}

在State模式中,我们使用类来表示白天和晚上,这样就不需要if语句来判断现在是白天还是晚上了。

大致的设计思路就是:(1)使用方法来判断状态;(2)使用类来表示状态。

下面是示例程序的具体实现。

  • 类和接口的一览表
名字 说明
State 表示金库状态的接口
DayState 表示“白天”状态的类。它实现了State接口
NightState 表示“晚上”状态的类。它实现了State接口
Context 表示管理金库状态,并与警报中心联系的接口
SafeFrame 实现了Context接口。在它内部持有按钮和画面显示等UI信息
Main 测试程序行为的类
  • 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);
}
  • DayState类
public class DayState implements State {
    private static DayState singleton = new DayState();

    private DayState() {}

    public static State getInstance() {
        return singleton;
    }

    @Override
    public void doClock(Context context, int hour) {
        if (hour < 9 || 17 <= hour) {
            context.changeState(NightState.getInstance());
        }
    }

    @Override
    public void doUse(Context context) {
        context.recordLog("使用金库(白天)");
    }

    @Override
    public void doAlarm(Context context) {
        context.callSecurityCenter("按下警铃(白天)");
    }

    @Override
    public void doPhone(Context context) {
        context.callSecurityCenter("正常通话(白天)");
    }

    @Override
    public String toString() {
        return "[白天]";
    }
}
  • NightState类
public class NightState implements State {
    private static NightState singleton = new NightState();

    private NightState() {}

    public static State getInstance() {
        return singleton;
    }

    @Override
    public void doClock(Context context, int hour) {
        if (9 <= hour && hour < 17) {
            context.changeState(DayState.getInstance());
        }
    }

    @Override
    public void doUse(Context context) {
        context.callSecurityCenter("紧急:晚上使用金库!");
    }

    @Override
    public void doAlarm(Context context) {
        context.callSecurityCenter("按下警铃(晚上)");
    }

    @Override
    public void doPhone(Context context) {
        context.recordLog("晚上的通话录音");
    }

    @Override
    public String toString() {
        return "[晚上]";
    }
}
  • Context接口
public interface Context {
    public abstract void setClock(int hour);

    public abstract void changeState(State state);

    public abstract void callSecurityCenter(String msg);

    public abstract void recordLog(String msg);
}
  • SafeFrame类
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

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("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());
        add(textClock, BorderLayout.NORTH);
        textClock.setEditable(false);
        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();
        setVisible(true);
        buttonUse.addActionListener(this::actionPerformed);
        buttonAlarm.addActionListener(this::actionPerformed);
        buttonPhone.addActionListener(this::actionPerformed);
        buttonExit.addActionListener(this::actionPerformed);
    }

    @Override
    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);
    }

    @Override
    public void changeState(State state) {
        System.out.println("从" + this.state + "状态变为了" + state + "状态。");
        this.state = state;
    }

    @Override
    public void callSecurityCenter(String msg) {
        textScreen.append("call!" + msg + "\n");
    }

    @Override
    public void recordLog(String msg) {
        textScreen.append("record..." + msg + "\n");
    }

    /**
     * Invoked when an action occurs.
     *
     * @param e
     */
    @Override
    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("?");
        }
    }
}
  • Main类
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) {
                    e.printStackTrace();
                }
            }
        }
    }
}

示例运行如下:

设计模式:State模式

现在时间是00:00[白天]状态变为了[晚上]状态。
现在时间是01:00
现在时间是02:00
现在时间是03:00
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=Use,when=1529504802951,modifiers=] on button0
现在时间是04:00
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=Alarm,when=1529504803727,modifiers=] on button1
现在时间是05:00
现在时间是06:00
现在时间是07:00
现在时间是08:00
现在时间是09:00[晚上]状态变为了[白天]状态。
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=Alarm,when=1529504808975,modifiers=] on button1
现在时间是10:00
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=Phone,when=1529504809911,modifiers=] on button2
现在时间是11:00
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=Phone,when=1529504810398,modifiers=] on button2
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=Alarm,when=1529504811239,modifiers=] on button1
现在时间是12:00
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=Use,when=1529504811743,modifiers=] on button0
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=Use,when=1529504812318,modifiers=] on button0
现在时间是13:00
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=Use,when=1529504812711,modifiers=] on button0
现在时间是14:00
现在时间是15:00
现在时间是16:00
现在时间是17:00

State模式中的角色

  • State(状态)

State角色表示状态,定义了根据不同状态进行不同处理的接口(API)。该接口(API)是那些处理内容依赖于状态的方法的集合。在示例程序中,由State接口扮演此角色。

  • ConcreteState(具体的状态)

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

  • Context(当前状况)

Context角色持有表示当前状态的ConcreteState角色。此外,它还定义了供外部调用者使用State模式的接口(API)。在示例程序中,由Context接口和SafeFrame类扮演此角色。


State模式的思路

分而治之

State模式实际上是一种分而治之的策略。当遇到庞大且复杂的问题时,我们会将该问题分解成为多个小问题,再逐个解决。

在不使用State模式时,我们需要使用条件分支语句判断当前的状态,然后进行相应的处理。状态越多,条件分支就会越多。而且,我们必须在所有的事件处理方法中都编写这样的条件分支语句。

使用State模式可以很方便地增加,删除和修改状态,而不需要修改很多的条件分支语句。

状态迁移

在使用State模式时应当注意如何来管理类的迁移

在示例程序中,虽然进行状态转移是Context角色调用的changeState方法,但是实际调用该方法的还是ConcreteState角色。

优点是我们将状态的迁移信息都集中到了一个类中,我们只需要查看ConcreteState类就可以知道状态的迁移关系。

缺点是ConcreteState角色必须知道其他的ConcreteState角色才可以实现状态的迁移,也就是说各个状态类之间的依赖关系得到了加强,不便于修改状态类。

我们也可以采用将所有的状态迁移管理转移到Context角色中,提高ConcreteState角色的独立性,可以使程序的体系结构更加清晰。


想了解更多关于设计模式:设计模式专栏
设计模式总结