天天看點

設計模式(十九)-狀态模式(State Pattern)——用類表示狀态

在面向對象變成中,是用類表示對象的。

在狀态模式(State Pattern)中,我們用類來表示狀态。

State.java

package com.test.dp.State.Sample;

//表示金庫狀态的接口
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.java

package com.test.dp.State.Sample;

//表示“白天”狀态的類。它實作了State接口
public class DayState implements State {
    private static DayState singleton = new DayState();
    private DayState() {                                // 構造函數的可見性是private
    }
    public static State getInstance() {                 // 擷取唯一執行個體
        return singleton;
    }
    public void doClock(Context context, int hour) {    // 設定時間
        if (hour < 9 || 17 <= hour) {
            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 "[白天]";
    }
}
           

NightState.java

package com.test.dp.State.Sample;

//表示“晚上”狀态的類。它實作了State接口
public class NightState implements State {
    private static NightState singleton = new NightState();
    private NightState() {                              // 構造函數的可見性是private
    }
    public static State getInstance() {                 // 擷取唯一執行個體
        return singleton;
    }
    public void doClock(Context context, int hour) {    // 設定時間
        if (9 <= hour && hour < 17) {
            context.changeState(DayState.getInstance());
        }
    }
    public void doUse(Context context) {                // 使用金庫
        context.callSecurityCenter("緊急:晚上使用金庫!");
    }
    public void doAlarm(Context context) {              // 按下警鈴
        context.callSecurityCenter("按下警鈴(晚上)");
    }
    public void doPhone(Context context) {              // 正常通話
        context.recordLog("晚上的通話錄音");
    }
    public String toString() {                          // 顯示表示類的文字
        return "[晚上]";
    }
}
           

Context.java

package com.test.dp.State.Sample;

//表示管理金庫狀态,并與報警中心聯系的接口
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.java

package com.test.dp.State.Sample;

import java.awt.Frame;
import java.awt.Label;
import java.awt.Color;
import java.awt.Button;
import java.awt.TextField;
import java.awt.TextArea;
import java.awt.Panel;
import java.awt.BorderLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

//實作了Context接口。在它内部持有按鈕和畫面顯示等UI資訊
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());
        //  配置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("call! " + msg + "\n");
    }
    // 在警報中心留下記錄
    public void recordLog(String msg) {
        textScreen.append("record ... " + msg + "\n");
    }
}
           

Main.java

package com.test.dp.State.Sample;

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) {
                }
            }
        }
    }
}
           

執行結果:

總結:

應用執行個體:1、打籃球的時候運動員可以有正常狀态、不正常狀态和超常狀态。2、曾侯乙編鐘中,'鐘是抽象接口','鐘A'等是具體狀态,'曾侯乙編鐘'是具體環境(Context)。

優點:1、封裝了轉換規則。 2、枚舉可能的狀态,在枚舉狀态之前需要确定狀态種類。3、将所有與某個狀态有關的行為放到一個類中,并且可以友善地增加新的狀态,隻需要改變對象狀态即可改變對象的行為。4、允許狀态轉換邏輯與狀态對象合成一體,而不是某一個巨大的條件語句塊。5、可以讓多個環境對象共享一個狀态對象,進而減少系統中對象的個數。

缺點:1、狀态模式的使用必然會增加系統類和對象的個數。 2、狀态模式的結構與實作都較為複雜,如果使用不當将導緻程式結構和代碼的混亂。3、狀态模式對"開閉原則"的支援并不太好,對于可以切換狀态的狀态模式,增加新的狀态類需要修改那些負責狀态轉換的源代碼,否則無法切換到新增狀态,而且修改某個狀态類的行為也需修改對應類的源代碼。

使用場景:1、行為随狀态改變而改變的場景。2、條件、分支語句的代替者。

注意事項:在行為受狀态限制的時候使用狀态模式,而且狀态不超過 5 個。