文章目錄
- 案例引入
- 介紹
- 基本介紹
- 登場角色
- 應用場景
- 案例實現
- 案例一
- 類圖
- 實現
- 案例二:借貸平臺源碼剖析
- 傳統方式實現分析
- 狀態修改流程
- 類圖
- 實現
- 案例三:金庫警報系統
- 系統的運行邏輯
- 偽代碼
- 傳統實現方式
- 使用狀態模式
- 類圖
- 實現
- 分析
- 問題
- 問題一
- 問題二
- 總結
- 文章說明
案例引入
請編寫程序完成APP抽獎活動具體要求如下:
- 假如每參加一次這個活動要扣除用戶50積分,中獎概率是10%
- 獎品數量固定,抽完就不能抽獎
- 活動有四個狀態: 可以抽獎、不能抽獎、發放獎品和獎品領完,活動的四個狀態轉換關系圖如下
一開始的狀態為“不能抽獎”,當扣除50積分成功之后,狀態就變成了“可以抽獎”狀態
介紹
基本介紹
- 狀態模式: 它主要用來解決對象在多種狀態轉換時,需要對外輸出不同的行為的問題。狀態和行為是一一對應的(如果處于A狀態,就擁有A狀態所擁有的行為和操作),狀態之間可以相互轉換
- 當一個對象的內在狀態改變時,允許改變其行為,這個對象看起來像是變成了另外一個類的對象
- 在狀態模式中,使用類來表示狀態,可以通過切換類來改變對象的狀態,當需要增加新的類時,也只需要增加新的類即可
登場角色
Context(上下文)
:用于維護State實例, 根據state的不同,實例對應的ConcreteState類也不同,這樣子State對象的方法也不同State(狀態)
:抽象狀態角色,定義多個接口ConcreteState(具體狀態)
:是具體狀態角色,根據自身的狀態來實現State接口的方法
應用場景
- 當一個事件或者對象有很多種狀態,狀態之間會相互轉換,對不同的狀態要求有不同的行為的時候,可以考慮使用狀態模式
案例實現
案例一
類圖
Activity類含有所有的狀態對象,各個狀態子類也含有Activity對象
實現
【抽象狀態類:State】
package com.atguigu.state;/*** 狀態抽象類** @author Administrator*/
public abstract class State {/*** 扣除積分 - 50*/public abstract void deductMoney();/*** 是否抽中獎品** @return*/public abstract boolean raffle();/*** 發放獎品*/public abstract void dispensePrize();}
【不能抽獎狀態】
package com.atguigu.state;/*** 不能抽獎狀態* @author Administrator**/
public class NoRaffleState extends State {/*** 初始化時傳入活動引用,扣除積分后改變其狀態*/RaffleActivity activity;public NoRaffleState(RaffleActivity activity) {this.activity = activity;}/*** 當前狀態可以扣積分,扣除后,將狀態設置成可以抽獎狀態*/@Overridepublic void deductMoney() {System.out.println("扣除50積分成功,您可以抽獎了");activity.setState(activity.getCanRaffleState());}/*** 當前狀態不能抽獎* @return*/@Overridepublic boolean raffle() {System.out.println("扣了積分才能抽獎喔!");return false;}/*** 當前狀態不能發獎品*/@Overridepublic void dispensePrize() {System.out.println("不能發放獎品");}
}
【可以抽獎的狀態】
package com.atguigu.state;import java.util.Random;/*** 可以抽獎的狀態** @author Administrator*/
public class CanRaffleState extends State {RaffleActivity activity;public CanRaffleState(RaffleActivity activity) {this.activity = activity;}/*** 已經扣除了積分,不能再扣*/@Overridepublic void deductMoney() {System.out.println("已經扣取過了積分");}/*** 可以抽獎, 抽完獎后,根據實際情況,改成新的狀態** @return*/@Overridepublic boolean raffle() {System.out.println("正在抽獎,請稍等!");Random r = new Random();int num = r.nextInt(10);// 10%中獎機會if (num == 0) {// 改變活動狀態為發放獎品 contextactivity.setState(activity.getDispenseState());return true;} else {System.out.println("很遺憾沒有抽中獎品!");// 改變狀態為不能抽獎activity.setState(activity.getNoRafflleState());return false;}}/*** 不能發放獎品*/@Overridepublic void dispensePrize() {System.out.println("沒中獎,不能發放獎品");}
}
【發放獎品的狀態】
package com.atguigu.state;/*** 發放獎品的狀態** @author Administrator*/
public class DispenseState extends State {/*** 初始化時傳入活動引用,發放獎品后改變其狀態*/RaffleActivity activity;public DispenseState(RaffleActivity activity) {this.activity = activity;}@Overridepublic void deductMoney() {System.out.println("不能扣除積分");}@Overridepublic boolean raffle() {System.out.println("不能抽獎");return false;}//發放獎品@Overridepublic void dispensePrize() {if (activity.getCount() > 0) {System.out.println("恭喜中獎了");// 改變狀態為不能抽獎activity.setState(activity.getNoRafflleState());} else {System.out.println("很遺憾,獎品發送完了");// 改變狀態為獎品發送完畢, 后面我們就不可以抽獎activity.setState(activity.getDispensOutState());//System.out.println("抽獎活動結束");//System.exit(0);}}
}
【獎品發放完畢狀態】
package com.atguigu.state;/*** 獎品發放完畢狀態* 說明,當我們activity 改變成 DispenseOutState, 抽獎活動結束** @author Administrator*/
public class DispenseOutState extends State {/*** 初始化時傳入活動引用*/RaffleActivity activity;public DispenseOutState(RaffleActivity activity) {this.activity = activity;}@Overridepublic void deductMoney() {System.out.println("獎品發送完了,請下次再參加");}@Overridepublic boolean raffle() {System.out.println("獎品發送完了,請下次再參加");return false;}@Overridepublic void dispensePrize() {System.out.println("獎品發送完了,請下次再參加");}
}
【運行】
--------第1次抽獎----------
扣除50積分成功,您可以抽獎了
正在抽獎,請稍等!
很遺憾沒有抽中獎品!
--------第2次抽獎----------
扣除50積分成功,您可以抽獎了
正在抽獎,請稍等!
很遺憾沒有抽中獎品!
--------第3次抽獎----------
扣除50積分成功,您可以抽獎了
正在抽獎,請稍等!
很遺憾沒有抽中獎品!
--------第4次抽獎----------
扣除50積分成功,您可以抽獎了
正在抽獎,請稍等!
很遺憾沒有抽中獎品!
--------第5次抽獎----------
扣除50積分成功,您可以抽獎了
正在抽獎,請稍等!
很遺憾沒有抽中獎品!
--------第6次抽獎----------
扣除50積分成功,您可以抽獎了
正在抽獎,請稍等!
很遺憾沒有抽中獎品!
--------第7次抽獎----------
扣除50積分成功,您可以抽獎了
正在抽獎,請稍等!
恭喜中獎了
--------第8次抽獎----------
扣除50積分成功,您可以抽獎了
正在抽獎,請稍等!
很遺憾沒有抽中獎品!
--------第9次抽獎----------
扣除50積分成功,您可以抽獎了
正在抽獎,請稍等!
很遺憾,獎品發送完了
--------第10次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第11次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第12次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第13次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第14次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第15次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第16次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第17次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第18次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第19次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第20次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第21次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第22次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第23次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第24次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第25次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第26次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第27次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第28次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第29次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加
--------第30次抽獎----------
獎品發送完了,請下次再參加
獎品發送完了,請下次再參加Process finished with exit code 0
案例二:借貸平臺源碼剖析
傳統方式實現分析
通過if/else判斷訂單的狀態,從而實現不同的邏輯
【分析】
這類代碼難以應對變化,在添加一種狀態時,我們需要手動添加if/else,在添加一種功能時,要對所有的狀態進行判斷。因此代碼會變得越來越臃腫,并且一旦沒有處理某個狀態便會發生極其嚴重的BUG,難以維護
【改進】
借貸平臺的訂單,有審核-發布-搶單
等等步驟,隨著操作的不同,會改變訂單的狀態,項目中的這個模塊實現就會使用到狀態模式
狀態修改流程
類圖
實現
【狀態枚舉類:StateEnum】
package com.atguigu.state.money;/*** 狀態枚舉類* @author Administrator**/
public enum StateEnum {//訂單生成GENERATE(1, "GENERATE"),//已審核REVIEWED(2, "REVIEWED"),//已發布PUBLISHED(3, "PUBLISHED"),//待付款NOT_PAY(4, "NOT_PAY"),//已付款PAID(5, "PAID"),//已完結FEED_BACKED(6, "FEED_BACKED");private int key;private String value;StateEnum(int key, String value) {this.key = key;this.value = value;}public int getKey() {return key;}public String getValue() {return value;}
}
【狀態接口:State】
package com.atguigu.state.money;/*** 狀態接口* @author Administrator**/
public interface State {/*** 電審*/void checkEvent(Context context);/*** 電審失敗*/void checkFailEvent(Context context);/*** 定價發布*/void makePriceEvent(Context context);/*** 接單*/void acceptOrderEvent(Context context);/*** 無人接單失效*/void notPeopleAcceptEvent(Context context);/*** 付款*/void payOrderEvent(Context context);/*** 接單有人支付失效*/void orderFailureEvent(Context context);/*** 反饋*/void feedBackEvent(Context context);String getCurrentState();
}
【抽象狀態類】
使用抽象狀態類來默認實現方法之后,具體的狀態類(其子類)可以只選擇所需要的方法來進行重寫
package com.atguigu.state.money;/*** 抽象類,默認實現了 State 接口的所有方法* 該類的所有方法,其子類(具體的狀態類),可以有選擇的進行重寫*/
public abstract class AbstractState implements State {protected static final RuntimeException EXCEPTION = new RuntimeException("操作流程不允許");@Overridepublic void checkEvent(Context context) {throw EXCEPTION;}@Overridepublic void checkFailEvent(Context context) {throw EXCEPTION;}@Overridepublic void makePriceEvent(Context context) {throw EXCEPTION;}@Overridepublic void acceptOrderEvent(Context context) {throw EXCEPTION;}@Overridepublic void notPeopleAcceptEvent(Context context) {throw EXCEPTION;}@Overridepublic void payOrderEvent(Context context) {throw EXCEPTION;}@Overridepublic void orderFailureEvent(Context context) {throw EXCEPTION;}@Overridepublic void feedBackEvent(Context context) {throw EXCEPTION;}
}
【所有的具體狀態類都在這個文件里面】
package com.atguigu.state.money;/*** 反饋狀態*/
class FeedBackState extends AbstractState {@Overridepublic String getCurrentState() {return StateEnum.FEED_BACKED.getValue();}
}/*** 通用狀態*/
class GenerateState extends AbstractState {@Overridepublic void checkEvent(Context context) {context.setState(new ReviewState());}@Overridepublic void checkFailEvent(Context context) {context.setState(new FeedBackState());}@Overridepublic String getCurrentState() {return StateEnum.GENERATE.getValue();}
}/*** 未支付狀態*/
class NotPayState extends AbstractState {@Overridepublic void payOrderEvent(Context context) {context.setState(new PaidState());}@Overridepublic void feedBackEvent(Context context) {context.setState(new FeedBackState());}@Overridepublic String getCurrentState() {return StateEnum.NOT_PAY.getValue();}
}/*** 已支付狀態*/
class PaidState extends AbstractState {@Overridepublic void feedBackEvent(Context context) {context.setState(new FeedBackState());}@Overridepublic String getCurrentState() {return StateEnum.PAID.getValue();}
}/*** 發布狀態*/
class PublishState extends AbstractState {@Overridepublic void acceptOrderEvent(Context context) {//接受訂單成功,把當前狀態設置為NotPayState//至于實際上應該變成哪個狀態,由流程圖來決定context.setState(new NotPayState());}@Overridepublic void notPeopleAcceptEvent(Context context) {context.setState(new FeedBackState());}@Overridepublic String getCurrentState() {return StateEnum.PUBLISHED.getValue();}
}/*** 回顧狀態*/
class ReviewState extends AbstractState {@Overridepublic void makePriceEvent(Context context) {context.setState(new PublishState());}@Overridepublic String getCurrentState() {return StateEnum.REVIEWED.getValue();}}
【環境上下文】
package com.atguigu.state.money;/*** 環境上下文*/
public class Context extends AbstractState{/*** 當前的狀態 state, 根據我們的業務流程處理,不停的變化*/private State state;@Overridepublic void checkEvent(Context context) {state.checkEvent(this);getCurrentState();}@Overridepublic void checkFailEvent(Context context) {state.checkFailEvent(this);getCurrentState();}@Overridepublic void makePriceEvent(Context context) {state.makePriceEvent(this);getCurrentState();}@Overridepublic void acceptOrderEvent(Context context) {state.acceptOrderEvent(this);getCurrentState();}@Overridepublic void notPeopleAcceptEvent(Context context) {state.notPeopleAcceptEvent(this);getCurrentState();}@Overridepublic void payOrderEvent(Context context) {state.payOrderEvent(this);getCurrentState();}@Overridepublic void orderFailureEvent(Context context) {state.orderFailureEvent(this);getCurrentState();}@Overridepublic void feedBackEvent(Context context) {state.feedBackEvent(this);getCurrentState();}public State getState() {return state;}public void setState(State state) {this.state = state;}@Overridepublic String getCurrentState() {System.out.println("當前狀態 : " + state.getCurrentState());return state.getCurrentState();}
}
【主類】
package com.atguigu.state.money;/*** 測試類*/
public class ClientTest {public static void main(String[] args) {//創建context 對象Context context = new Context();//將當前狀態設置為 PublishStatecontext.setState(new PublishState());System.out.println(context.getCurrentState());// //publish --> not paycontext.acceptOrderEvent(context);
// //not pay --> paidcontext.payOrderEvent(context);
// // 失敗, 檢測失敗時,會拋出異常
// try {
// context.checkFailEvent(context);
// System.out.println("流程正常..");
// } catch (Exception e) {
// System.out.println(e.getMessage());
// }}}
【運行】
當前狀態 : PUBLISHED
PUBLISHED
當前狀態 : NOT_PAY
當前狀態 : PAIDProcess finished with exit code 0
案例三:金庫警報系統
系統的運行邏輯
偽代碼
傳統實現方式
使用狀態模式
類圖
實現
【狀態接口】
package com.atguigu.state.Sample;public interface State {/*** 設置時間** @param context* @param hour*/public abstract void doClock(Context context, int hour);/*** 使用金庫** @param context*/public abstract void doUse(Context context);/*** 按下警鈴** @param context*/public abstract void doAlarm(Context context);/*** 正常通話** @param context*/public abstract void doPhone(Context context);
}
【白天狀態】
package com.atguigu.state.Sample;
/*** 表示白天的狀態*/
public class DayState implements State {/*** 每個狀態都是一個類,如果每次改變狀態都需要生成一個新的實例的話,比較浪費內存和時間,所以在這里使用單例模式*/private static DayState singleton = new DayState();/*** 構造函數的可見性是private*/private DayState() {}/*** 獲取唯一實例* @return*/public static State getInstance() {return singleton;}/*** 設置時間* @param context* @param hour*/public void doClock(Context context, int hour) {if (hour < 9 || 17 <= hour) {// 如果時間是晚上,切換到夜間狀態context.changeState(NightState.getInstance());}}/*** 使用金庫* @param context*/public void doUse(Context context) {context.recordLog("使用金庫(白天)");}/*** 按下警鈴* @param context*/public void doAlarm(Context context) {context.callSecurityCenter("按下警鈴(白天)");}/*** 正常通話* @param context*/public void doPhone(Context context) {context.callSecurityCenter("正常通話(白天)");}/*** 顯示表示類的文字* @return*/public String toString() {return "[白天]";}
}
【夜間狀態】
package com.atguigu.state.Sample;public class NightState implements State {private static NightState singleton = new NightState();/*** 構造函數的可見性是private*/private NightState() {}/*** 獲取唯一實例* @return*/public static State getInstance() { return singleton;}/*** 設置時間* @param context* @param hour*/public void doClock(Context context, int hour) { if (9 <= hour && hour < 17) {context.changeState(DayState.getInstance());}}/*** 使用金庫* @param context*/public void doUse(Context context) { context.callSecurityCenter("緊急:晚上使用金庫!");}/*** 按下警鈴* @param context*/public void doAlarm(Context context) { context.callSecurityCenter("按下警鈴(晚上)");}/*** 正常通話* @param context*/public void doPhone(Context context) { context.recordLog("晚上的通話錄音");}/*** 顯示表示類的文字* @return*/public String toString() { return "[晚上]";}
}
【Context接口】
- 負責管理狀態和聯系警報中心
package com.atguigu.state.Sample;public interface Context {/*** 設置時間** @param hour*/public abstract void setClock(int hour);/*** 改變狀態** @param state*/public abstract void changeState(State state);/*** 聯系警報中心** @param msg*/public abstract void callSecurityCenter(String msg);/*** 在警報中心留下記錄** @param msg*/public abstract void recordLog(String msg);
}
【Context角色:SafeFrame】
在這個實例程序中,Context角色的作用被Context接口和SafeFrame類分擔了。Context接口定義了供外部調用者使用狀態模式的接口,而SafeFrame類持有表示當前狀態的ConcreteState角色
package com.atguigu.state.Sample;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("使用金庫");/*** 按下警鈴按鈕*/private Button buttonAlarm = new Button("按下警鈴");/*** 正常通話按鈕*/private Button buttonPhone = new Button("正常通話");/*** 結束按鈕*/private Button buttonExit = new Button("結束");/*** 金庫當前的狀態*/private State state = DayState.getInstance();/*** 構造函數** @param title*/public SafeFrame(String title) {super(title);setBackground(Color.lightGray);setLayout(new BorderLayout());// 配置textClockadd(textClock, BorderLayout.NORTH);textClock.setEditable(false);// 配置textScreenadd(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);}/*** 按鈕被按下后,該方法會被調用** @param e*/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("?");}}/*** 設置時間** @param hour*/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);}/*** 改變狀態** @param state*/public void changeState(State state) {System.out.println("從" + this.state + "狀態變為了" + state + "狀態。");this.state = state;}/*** 聯系警報中心** @param msg*/public void callSecurityCenter(String msg) {textScreen.append("call! " + msg + "\n");}/*** 在警報中心留下記錄** @param msg*/public void recordLog(String msg) {textScreen.append("record ... " + msg + "\n");}
}
【運行】
package com.atguigu.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) {}}}}
}
【運行】
現在時間是00:00
從[白天]狀態變為了[晚上]狀態。
現在時間是01:00
現在時間是02:00
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=使用金庫,when=1691920919394,modifiers=] on button0
現在時間是03:00
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=按下警鈴,when=1691920920040,modifiers=] on button1
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=正常通話,when=1691920920824,modifiers=] on button2
現在時間是04:00
現在時間是05:00
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=按下警鈴,when=1691920922071,modifiers=] on button1
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=使用金庫,when=1691920922626,modifiers=] on button0
現在時間是06:00
現在時間是07:00
現在時間是08:00
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=使用金庫,when=1691920925446,modifiers=] on button0
現在時間是09:00
從[晚上]狀態變為了[白天]狀態。
現在時間是10:00
現在時間是11:00
現在時間是12:00
現在時間是13:00
現在時間是14:00
現在時間是15:00
Disconnected from the target VM, address: '127.0.0.1:1966', transport: 'socket'Process finished with exit code 130
分析
上面的實現方式中,由具體狀態類來實際調用方法切換到另一個狀態,如DayState類的doClock方法,這種方式既有優點又有缺點:
- 優點:當我們想知道“什么時候從DayState的類變化為其他狀態”,只需要查看DayState類即可
- 缺點:每個ConcreteState角色都需要知道其他ConcreteState角色的方法,各個類之間的依賴關系較強,如果刪除了一個ConcreteState類,就需要修改其他的ConcreteState類
除了這種實現方式之外,也可以將所有的狀態遷移交給扮演Context角色的類來負責,這樣可以提高ConcreteState角色的獨立性,程序的整體結構也會更加清晰,當然,這樣做需要Context角色知道所有的ConcreteState角色,可以使用中介者模式來改進
問題
問題一
問:將Context定義為抽象類而非接口,然后讓Context類持有state字段這樣更符合狀態模式的設計思想。但是在示例程序中我們并沒有這么做,而是將Context角色定義為Context接口,讓SafeFrame類持有state字段,請問這是為什么呢?
答:Java中只能單一繼承,所以如果將Context角色定義為類,那么由于SafeFrame類已經是Frame類的子類了,它將無法再繼承Context 類。不過,如果另外編寫一個Context類的子類,并將它的實例保存在SafeFrame類的字段中那么通過將處理委托給這個實例是可以實現上述問題的需求的。
問題二
請在示例程序中增加一個新的“緊急情況”狀態。不論是什么時間,只要處于“緊急情況”下,就向警報中心通知緊急情況
- 按下警鈴后,系統狀態變為“緊急情況”狀態
- 如果“緊急情況”下使用金庫的話,會向警報中心通知緊急情況(與當時的時間無關)
- 如果“緊急情況”下按下警鈴的話,會向警報中心通知緊急情況(與當時的時間無關)
- 如果“緊急情況”下使用電話的話,會呼叫警報中心的留言電話(與當時的時間無關)
【增加一個緊急狀態類】
package com.atguigu.state.A4;public class UrgentState implements State {private static UrgentState singleton = new UrgentState();private UrgentState() {}public static State getInstance() {return singleton;}public void doClock(Context context, int hour) {// 設置時間// 在設置時間處理中什么都不做 }public void doUse(Context context) {// 使用金庫context.callSecurityCenter("緊急:緊急時使用金庫!");}public void doAlarm(Context context) {// 按下警鈴context.callSecurityCenter("按下警鈴(緊急時)");}public void doPhone(Context context) {// 正常通話context.callSecurityCenter("正常通話(緊急時)");}public String toString() {// 顯示字符串return "[緊急時]";}
}
【修改其他狀態的狀態遷移方法】
package com.atguigu.state.A4;public class DayState implements State {private static DayState singleton = new DayState();private DayState() { }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("按下警鈴(白天)");// 只需要看這里就行,一旦按下緊鈴,就會進入到緊急狀態context.changeState(UrgentState.getInstance()); }public void doPhone(Context context) { context.callSecurityCenter("正常通話(白天)");}public String toString() { return "[白天]";}
}
夜間狀態也需要修改對應的狀態遷移方法,和白天狀態類似,這里就不再展示了
總結
【優點】
- 代碼有很強的可讀性,狀態模式將每個狀態的行為封裝到對應的一個類中方便維護
- 將容易產生問題的if-else語句刪除了,如果把每個狀態的行為都放到一個類中,每次調用方法時都要判斷當前是什么狀態,不但會產出很多if-else語句,而且容易出錯
- 符合“開閉原則”,容易增刪狀態,只需要增刪一個ConcreteState的類,然后修改負責狀態遷移的類即可。如果使用的是傳統方式,新增一個狀態,就需要增加很多的判斷語句
- 使用“分而治之”的思想,將多個狀態分開來,每個類只需要根據當前狀態來寫代碼即可,不需要在執行事件之前寫復雜的條件分支語句
- 如果需要增加依賴于狀態的處理方法,只需要在State接口中增加新的方法,并讓所有的ConcreteState類實現這個方法,雖然修改量較大,但是開發者肯定不會忘記去實現這個方法,因為不實現,編譯就會報錯
【缺點】
- 會產生很多類。每個狀態都要一個對應的類,當狀態過多時會產生很多類,加大維護難度
文章說明
- 本文章為本人學習尚硅谷的學習筆記,文章中大部分內容來源于尚硅谷視頻(點擊學習尚硅谷相關課程),也有部分內容來自于自己的思考,發布文章是想幫助其他學習的人更方便地整理自己的筆記或者直接通過文章學習相關知識,如有侵權請聯系刪除,最后對尚硅谷的優質課程表示感謝。
- 本人還同步閱讀《圖解設計模式》書籍(圖解設計模式/(日)結城浩著;楊文軒譯–北京:人民郵電出版社,2017.1),進而綜合兩者的內容,讓知識點更加全面