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 || 測試程式行為的類
- 使用與不使用狀態模式對比
- 不使用
使用金庫時被呼叫的方法(){ if(白天){ 向警報中心報告使用記錄 ]elseif(晚上){ 向警報中心報告緊急事態 警鈴響起時被呼叫的方法(){ 向警報中心報告緊急事態 正常通話時被呼叫的方法(){ if(白天){ 呼叫警報中心 }elseif(晚上){ 呼叫警報中心的留言電話 }
- 使用
表示百天的狀態的類{ 使用金庫時被呼叫的方法(){ 向警報中心報告使用記錄 警鈴響起時被呼叫的方法(){ 向警報中心報告緊急事態 正常通話時被呼叫的方法(){ 呼叫警報中心 表示晚上的狀態的類{ 使用金庫時被呼叫的方法(){ 向警報中心報告緊急事態 警鈴響起時被呼叫的方法(){ 向警報中心報告緊急事態 正常通話時被呼叫的方法(){ 呼叫警報中心的留言電話 - 相關設計模式 ◆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"); }
}
···