【設計模式——學習筆記】23種設計模式——狀態模式State(原理講解+應用場景介紹+案例介紹+Java代碼實現)

文章目錄

  • 案例引入
  • 介紹
    • 基本介紹
    • 登場角色
    • 應用場景
  • 案例實現
    • 案例一
      • 類圖
      • 實現
    • 案例二:借貸平臺源碼剖析
      • 傳統方式實現分析
      • 狀態修改流程
      • 類圖
      • 實現
    • 案例三:金庫警報系統
      • 系統的運行邏輯
      • 偽代碼
        • 傳統實現方式
        • 使用狀態模式
      • 類圖
      • 實現
      • 分析
      • 問題
        • 問題一
        • 問題二
  • 總結
  • 文章說明

案例引入

請編寫程序完成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),進而綜合兩者的內容,讓知識點更加全面

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/37629.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/37629.shtml
英文地址,請注明出處:http://en.pswp.cn/news/37629.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

國內芯片廠商創新突破,助力國產替代持續加速

近日&#xff0c;中商產業研究院發布最新研究報告顯示&#xff0c;今年1~5月份中國進口集成電路為1865億件&#xff0c;同比下降19.6%&#xff0c;同比去年5個月累計少進口了455億顆&#xff0c;平均每天少進口3億顆。與此同時&#xff0c;英特爾、AMD、美光、三星、SK海力士等…

OSI七層模型和TCP/IP四層模型

OSI七層模型和TCP/IP四層模型 七層模型(OSI) OSI七層模型&#xff08;Open Systems Interconnection Reference Model&#xff09;是一個用于計算機網絡體系結構的標準化框架&#xff0c;旨在定義網絡通信中不同層次的功能和協議。 各個層次具體如下&#xff1a; 物理層&am…

C語言 冒泡排序

目錄 一、原理 二、代碼演示 三、代碼優化 一、原理 假設&#xff1a; int arr[] { 9,8,7,6,5,4,3,2,1,0 }; 將 arr 內的元素進行升序排列&#xff0c;得到一個新的數組 int arr[] { 0&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;…

windows docker mysql8.0 掛載配置文件不生效的問題

原因 mysql 8.0 遇到sql_modeonly_full_group_by的問題&#xff0c;于是就自定義my.cnf 去掉only_full_group_by&#xff0c;修改my.cnf 文件后&#xff0c;進行映射啟動 docker run 命令 docker run -p 3306:3306 --privilegedtrue --restartalways -d --name axsc-mysql -…

【0814作業】多線程并發服務器

1) 代碼 #include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <arpa/inet.h> #include <string.h> #include <stdlib.h> #include <signal.h> #include <sys/wait.h> #include <netinet/in.h>…

配置文件優先級解讀

目錄 概述 同級目錄application配置文件優先級 application 以及bootstrap 優先級 不同級目錄配置文件優先級 外部配置加載順序 概述 SpringBoot除了支持properties格式的配置文件&#xff0c;還支持另外兩種格式的配置文件。三種配置文件格式分別如下: properties格式…

Python學習筆記_基礎篇(二)_數據類型之字符串

一.基本數據類型 整數&#xff1a;int 字符串&#xff1a;str(注&#xff1a;\t等于一個tab鍵) 布爾值&#xff1a; bool 列表&#xff1a;list 列表用[] 元祖&#xff1a;tuple 元祖用&#xff08;&#xff09; 字典&#xff1a;dict 注&#xff1a;所有的數據類型都存在想對應…

TFTP Server

簡介 TFTP&#xff08;Trivial File Transfer Protocol,簡單文件傳輸協議&#xff09;是TCP/IP協議族中的一個用來在客戶機與服務器之間進行簡單文件傳輸的協議&#xff0c;提供不復雜、開銷不大的文件傳輸服務。端口號為69。 TFTP和FTP的區別 安全性區別 FTP支持登錄安全&…

ATF(TF-A)安全通告 TFV-9 (CVE-2022-23960)

ATF(TF-A)安全通告匯總 目錄 一、ATF(TF-A)安全通告 TFV-9 (CVE-2022-23960) 二、CVE-2022-23960 一、ATF(TF-A)安全通告 TFV-9 (CVE-2022-23960) Title TF-A披露通過分支預測目標重用&#xff08;branch prediction target reuse&#xff09;引發的前瞻執行處理器漏洞 CV…

Softmax Strategy

1. epsilon-greedy strategy 11111 2. UCB strategy 222 3. Softmax strategy 333 4. Gradient strategy 444 References [1] 科學網—【RL系列】Multi-Armed Bandit筆記——Softmax選擇策略 - 管金昱的博文 [2] The Epsilon-Greedy Algorithm | James D. McCaffrey

【軟考】2023系統架構設計師考試

目錄 1 軟考資格設置 2 考試報名 3 考試準備 4 參加考試 5 考試感受 6 其他 1 軟考資格設置 2 考試報名 報名網址&#xff1a;https://www.ruankao.org.cn/ 3 考試準備 4 參加考試 2023年下半年系統架構設計師考試時間為11月4、5日。 5 考試感受 6 其他 最近好像有地區…

vue element 多圖片組合預覽

定義組件&#xff1a;preview-image <template><div><div class"imgbox"><divclass"preview-img":class"boxClass"v-if"Imageslist 3 ||Imageslist 5 ||Imageslist 7 ||Imageslist 8 ||Imageslist > 9"&…

LangChain手記 Models,Prompts and Parsers

整理并翻譯自DeepLearning.AILangChain的官方課程&#xff1a;Models,Prompts and Parsers 模型&#xff0c;提示詞和解析器&#xff08;Models, Prompts and Parsers&#xff09; 模型&#xff1a;大語言模型提示詞&#xff1a;構建傳遞給模型的輸入的方式解析器&#xff1a;…

即時編譯的觸發

文章目錄 Java即時編譯:概述Java即時編譯:原理Java即時編譯:優點提高程序的運行效率動態優化高效利用CPU資源Java即時編譯:案例分析Java即時編譯:概述 Java語言一直是業界公認的高級編程語言,在眾多編程語言中獨樹一幟。Java的JVM(Java虛擬機)是Java語言的核心所在,它…

即將發布的 Kibana 版本可運行 Node.js 18

作者&#xff1a;Thomas Watson Kibana 構建在 Node.js 框架之上。 為了確保每個 Kibana 版本的穩定性和使用壽命&#xff0c;我們始終將捆綁的 Node.js 二進制文件保持為最新的最新長期支持 (LTS) 版本。 當 Node.js 版本 18 升級到 LTS 時&#xff0c;我們開始將 Kibana 升級…

如何理解“對矩陣進行初等行變換不改變其列向量的線性關系”?

對矩陣A進行初等行變換相當于左乘一個可逆矩陣P。 把A看作是列向量組&#xff0c;若有Ax0&#xff0c;則其中的x就說明了列向量的線性關系&#xff1a; [ α 1 , α 2 , α 3 ] [ x 1 x 2 x 3 ] [ 0 ] \left[ \alpha_1 ,\alpha_2, \alpha_3 \right] \begin{bmatrix} x_1\\ x…

【爬蟲】爬取旅行評論和評分

以馬蜂窩“普達措國家公園”為例&#xff0c;其評論高達3000多條&#xff0c;但這3000多條并非是完全向用戶展示的&#xff0c;向用戶展示的只有5頁&#xff0c;數了一下每頁15條評論&#xff0c;也就是75條評論&#xff0c;有點太少了吧&#xff01; 因此想了個辦法盡可能多爬…

【 BERTopic應用 02/3】 分析卡塔爾世界杯推特數據

攝影&#xff1a;Fauzan Saari on Unsplash 一、說明 這是我們對世界杯推特數據分析的第3部分&#xff0c;我們放棄了。我們將對我們的數據進行情緒分析&#xff0c;以了解人們對卡塔爾世界杯的感受。我將在這里介紹的一個功能強大的工具包是Hugging Face&#xff0c;您可以在…

高憶管理:策略:短期利空落地 市場有望企穩回升

高憶管理指出&#xff0c;基于A股商場出資環境分析&#xff0c;尤其是當時商場存量博弈為主的布景下&#xff0c;主張重視以下“21”主線輪動&#xff1a;&#xff08;1&#xff09;國產科技代替立異&#xff1a;電子&#xff08;半導體、消費電子&#xff09;、通信&#xff0…

Vue基本知識

一、vue入門 Vue為前端的框架&#xff0c;免除了原生js的DOM操作。簡化書寫。 基于MVVM的思想&#xff0c;實現數據的雙向綁定&#xff0c;使編程的重點放在數據上。 1、引入vue.js文件 2、定義vue核心對象&#xff0c;定義數據模型 3、編寫視圖 //1、引入vue.js <scr…