1. 程式人生 > >State模式(狀態設計模式)

State模式(狀態設計模式)

State???

State模式中,我們用類來表示狀態。以類來表示狀態後,我們就能通過切換類來方便地改變物件的狀態。當需要增加新的狀態時,如何修改程式碼這個問題也會很明確。

  • 直接用狀態代替硬編碼 依賴於狀態的處理,來執行具體的操作

理清職責

  • 實現功能:
·有一個金庫
·金庫與警報中心相連
·金庫裡有警鈴和正常通話用的電話·金庫裡有時鐘,監視著現在的時間
·白天的時間範圍是9:00~16:59,晚上的時間範圍是17:00~23:59和0:00~8:59
·金庫只能在白天使用
·白天使用金庫的話,會在警報中心留下記錄
·晚上使用金庫的話,會向警報中心傳送緊急事態通知
·任何時候都可以使用警鈴
·使用警鈴的話,會向警報中心傳送緊急事態通知
·任何時候都可以使用電話(但晚上只有留言電話)
·白天使用電話的話,會呼叫警報中心
·晚上用電話的話,會呼叫警報中心的留言電話

名字=======》》》》》說明 State ||表示金庫狀態的介面 DayState ||表示“白天”狀態的類。它實現了State介面 NightState ||表示“晚上”狀態的類。它實現了State介面 Context ||表示管理金庫狀態,並與警報中心聯絡的介面 SafeFrame ||實現了Context介面。在它內部持有按鈕和畫面顯示等UI資訊 Main || 測試程式行為的類

  • 使用與不使用狀態模式對比
  1. 不使用
使用金庫時被呼叫的方法(){
if(白天){
向警報中心報告使用記錄
]elseif(晚上){
向警報中心報告緊急事態
警鈴響起時被呼叫的方法(){
向警報中心報告緊急事態
正常通話時被呼叫的方法(){
if(白天){
呼叫警報中心
}elseif(晚上){
呼叫警報中心的留言電話
}

  1. 使用
表示百天的狀態的類{
使用金庫時被呼叫的方法(){
向警報中心報告使用記錄
警鈴響起時被呼叫的方法(){
向警報中心報告緊急事態
正常通話時被呼叫的方法(){
呼叫警報中心
表示晚上的狀態的類{
使用金庫時被呼叫的方法(){
向警報中心報告緊急事態
警鈴響起時被呼叫的方法(){
向警報中心報告緊急事態
正常通話時被呼叫的方法(){
呼叫警報中心的留言電話

- 相關設計模式
◆Singleton模式(第5章)Singleton 模式常常會出現在ConcreteState角色中。在示例程式中,我們就使用了Singleton模式。這是因為在表示狀態的類中並沒有定義任何例項欄位(即表示例項的狀態的欄位)。
◆Flyweight模式(第20章)在表示狀態的類中並沒有定義任何例項欄位。因此,有時我們可以使用Flyweight模式在多個Context 角色之間共享ConcreteState角色。

UML

時序圖:

Code

  • DayState \NightState State
public interface State {

    //設定時間
    void doclock(Context context, int hour);

    // 使用金庫
    void doUse(Context context);

    // 按下警鈴
    void doAlarm(Context context);

    // 正常通話
    void dophone(Context context);

}

public class NightState implements State {

    private NightState() {
    }

    private static NightState singleton = new NightState();

    public static State getInstance() {
        return (State) singleton;
    }

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

    @Override
    public void doUse(Context context) {
        context.recordLog("使用金庫[晚上]");
    }

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

    @Override
    public void dophone(Context context) {
        context.recordLog("正常通話[晚上]");
    }

    @Override
    public String toString() {
        return "DayState{晚上}";
    }

}

public class DayState implements State {

    /**
     * 這裡使用單例模式,因為每次改變一次狀態都會生成一次例項,非常浪費記憶體與時間
     */
    private DayState() {
    }

    private static DayState singleton = new DayState();


    public static State getInstance() {
        return singleton;
    }

    @Override
    public void doclock(Context context, int hour) {
        if (hour < 9 || hour >= 17) {
            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.recordLog("正常通話[白天]");
    }

    @Override
    public String toString() {
        return "DayState{白天}";
    }
}


  • Context 、SateFrame 、MainT

···

public class MainT {

public static void main(String[] args) {
    SateFrame frame = new SateFrame("Safe Smaple");

    // 24個小時制
    while (true){
        for (int i = 0; i < 24; i++) {
            frame.setClock(i);
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }

        }
    }
}

}

public interface Context { //設定時間 void setClock(int hour);

// 改變狀態
void changeState(State state);

// 聯絡警報中心
void callSecurityCenter(String msg);

// 在警報中心留下記錄
void recordLog(String msg);

} public class SateFrame 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 SateFrame(String title) throws HeadlessException {
    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();
    show();
    buttonUse.addActionListener(this);
    buttonALarm.addActionListener(this);
    buttonPhone.addActionListener(this);
    buttonExit.addActionListener(this);
}

/**
 * 可以看出這裡的操作就簡化很多了:
 * 基本只有業務邏輯程式碼:
 * 判斷狀態相關的程式碼可以直接由相關的狀態程式碼實現,
 * 即為由類的狀態代替了if else程式碼
 */

@Override
public void actionPerformed(ActionEvent e) {
    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("?");
    }
}

@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("呼叫---"+msg+"\n");
}

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

}

···