在面向對象變成中,是用類表示對象的。
在狀态模式(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 個。