24.1 APP 抽獎活動問題
請編寫程式完成 APP 抽獎活動 具體要求如下:
1) 假如每參加一次這個活動要扣除使用者 50 積分,中獎概率是 10%
2) 獎品數量固定,抽完就不能抽獎
3) 活動有四個狀態: 可以抽獎、不能抽獎、發放獎品和獎品領完
4) 活動的四個狀態轉換關係圖(下圖)
24.1 狀態模式基本介紹
基本介紹
1) 狀態模式(State Pattern):它主要用來解決物件在多種狀態轉換時,需要對外輸出不同的行為的問題。狀態和行為是一一對應的,狀態之間可以相互轉換
2) 當一個物件的內在狀態改變時,允許改變其行為,這個物件看起來像是改變了其類
24.2 狀態模式的原理類圖
- 對原理類圖的說明-即(狀態模式的角色及職責)
1) Context 類為環境角色, 用於維護 State 例項,這個例項定義當前狀態
2) State 是抽象狀態角色,定義一個介面封裝與 Context 的一個特點介面相關行為
3) ConcreteState 具體的狀態角色,每個子類實現一個與 Context 的一個狀態相關行為
24.1 狀態模式解決 APP 抽獎問
1) 應用例項要求
完成 APP 抽獎活動專案,使用狀態模式.
2) 思路分析和圖解(類圖)
- 定義出一個介面叫狀態介面,每個狀態都實現它。
- 介面有扣除積分方法、抽獎方法、發放獎品方法
3)程式碼實現
package com.lin.state; /** * 狀態抽象類 * * @author Administrator * */ public abstract class State { // 扣除積分 - 50 public abstract void deductMoney(); // 是否抽中獎品 public abstract boolean raffle(); // 發放獎品 public abstract void dispensePrize(); }
package com.lin.state; /** * 抽獎活動 // * * @author Administrator * */ public class RaffleActivity { // state 表示活動當前的狀態,是變化 State state = null; // 獎品數量 int count = 0; // 四個屬性,表示四種狀態 State noRafflleState = new NoRaffleState(this); State canRaffleState = new CanRaffleState(this); State dispenseState = new DispenseState(this); State dispensOutState = new DispenseOutState(this); // 構造器 // 1. 初始化當前的狀態為 noRafflleState(即不能抽獎的狀態) // 2. 初始化獎品的數量 public RaffleActivity(int count) { this.state = getNoRafflleState(); this.count = count; } // 扣分, 呼叫當前狀態的 deductMoney public void debuctMoney() { state.deductMoney(); } // 抽獎 public void raffle() { // 如果當前的狀態是抽獎成功 if (state.raffle()) { // 領取獎品state.dispensePrize(); } } public State getState() { return state; } public void setState(State state) { this.state = state; } // 這裡請大家注意,每領取一次獎品,count-- public int getCount() { int curCount = count; count--; return curCount; } public void setCount(int count) { this.count = count; } public State getNoRafflleState() { return noRafflleState; } public void setNoRafflleState(State noRafflleState) { this.noRafflleState = noRafflleState; } public State getCanRaffleState() { return canRaffleState; } public void setCanRaffleState(State canRaffleState) { this.canRaffleState = canRaffleState; } public State getDispenseState() { return dispenseState; } public void setDispenseState(State dispenseState) { this.dispenseState = dispenseState; } public State getDispensOutState() { return dispensOutState; } public void setDispensOutState(State dispensOutState) { this.dispensOutState = dispensOutState; } }
package com.lin.state; /** *不能抽獎狀態 *@author Administrator * */ public class NoRaffleState extends State { // 初始化時傳入活動引用,扣除積分後改變其狀態 RaffleActivity activity; public NoRaffleState(RaffleActivity activity) { this.activity = activity; } // 當前狀態可以扣積分 , 扣除後,將狀態設定成可以抽獎狀態 @Override public void deductMoney() { System.out.println("扣除 50 積分成功,您可以抽獎了"); activity.setState(activity.getCanRaffleState()); } // 當前狀態不能抽獎 @Override public boolean raffle() { System.out.println("扣了積分才能抽獎喔!"); return false; } // 當前狀態不能發獎品 @Override public void dispensePrize() { System.out.println("不能發放獎品"); } }
package com.lin.state; /** * 發放獎品的狀態 * * @author Administrator * */ public class DispenseState extends State { // 初始化時傳入活動引用,發放獎品後改變其狀態 RaffleActivity activity; public DispenseState(RaffleActivity activity) { this.activity = activity; } // @Override public void deductMoney() { System.out.println("不能扣除積分"); } @Override public boolean raffle() { System.out.println("不能抽獎"); return false; } //發放獎品 @Override public 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.lin.state; /** * 獎品發放完畢狀態 說明,當我們 activity 改變成 DispenseOutState, 抽獎活動結束 * * @author Administrator * */ public class DispenseOutState extends State { // 初始化時傳入活動引用 RaffleActivity activity; public DispenseOutState(RaffleActivity activity) { this.activity = activity; } @Override public void deductMoney() { System.out.println("獎品傳送完了,請下次再參加"); } @Override public boolean raffle() { System.out.println("獎品傳送完了,請下次再參加"); return false; } @Override public void dispensePrize() { System.out.println("獎品傳送完了,請下次再參加"); } }
package com.lin.state; import java.util.Random; /** * 可以抽獎的狀態 * * @author Administrator * */ public class CanRaffleState extends State { RaffleActivity activity; public CanRaffleState(RaffleActivity activity) { this.activity = activity; } // 已經扣除了積分,不能再扣 @Override public void deductMoney() { System.out.println("已經扣取過了積分"); } // 可以抽獎, 抽完獎後,根據實際情況,改成新的狀態 @Override public boolean raffle() { System.out.println("正在抽獎,請稍等!"); Random r = new Random(); int num = r.nextInt(10); // 10%中獎機會 if (num == 0) { // 改 變 活 動 狀 態 為 發 放 獎 品 context activity.setState(activity.getDispenseState()); return true; } else { System.out.println("很遺憾沒有抽中獎品!"); // 改變狀態為不能抽獎 activity.setState(activity.getNoRafflleState()); return false; } } // 不能發放獎品 @Override public void dispensePrize() { System.out.println("沒中獎,不能發放獎品"); } }
package com.lin.state; /** * 狀態模式測試類 * * @author Administrator * */ public class Client { public static void main(String[] args) { // 建立活動物件,獎品有 1 個獎品 RaffleActivity activity = new RaffleActivity(1); // 我們連續抽 300 次獎 for (int i = 0; i < 40; i++) { System.out.println("--------第" + (i + 1) + "次抽獎----------"); // 參加抽獎,第一步點選扣除積分 activity.debuctMoney(); // 第二步抽獎 activity.raffle(); } } }
24.1 狀態模式在實際專案-借貸平臺 原始碼剖析
1) 借貸平臺的訂單,有稽核-釋出-搶單 等等 步驟,隨著操作的不同,會改變訂單的狀態, 專案中的這個模組實現就會使用到狀態模式
2) 通常通過 if/else 判斷訂單的狀態,從而實現不同的邏輯,虛擬碼如下
3)程式碼實現
package com.lin.state.project; /** * 狀態介面 * * @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.lin.state.project; /** * 狀態列舉類 * * @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; } }
package com.lin.state.project; public abstract class AbstractState implements State { protected static final RuntimeException EXCEPTION = new RuntimeException("操作流程不允許"); //抽象類,預設實現了 State 介面的所有方法 //該類的所有方法,其子類(具體的狀態類),可以有選擇的進行重寫 @Override public void checkEvent(Context context) { throw EXCEPTION; } @Override public void checkFailEvent(Context context) { throw EXCEPTION; } @Override public void makePriceEvent(Context context) { throw EXCEPTION; } @Override public void acceptOrderEvent(Context context) { throw EXCEPTION; } @Override public void notPeopleAcceptEvent(Context context) { throw EXCEPTION; } @Override public void payOrderEvent(Context context) { throw EXCEPTION; } @Override public void orderFailureEvent(Context context) { throw EXCEPTION; } @Override public void feedBackEvent(Context context) { throw EXCEPTION; } }
package com.lin.state.project; //環境上下文 public class Context extends AbstractState { //當前的狀態 state, 根據我們的業務流程處理,不停的變化 private State state; @Override public void checkEvent(Context context) { state.checkEvent(this); getCurrentState(); } @Override public void checkFailEvent(Context context) { state.checkFailEvent(this); getCurrentState(); } @Override public void makePriceEvent(Context context) { state.makePriceEvent(this); getCurrentState(); } @Override public void acceptOrderEvent(Context context) { state.acceptOrderEvent(this); getCurrentState(); } @Override public void notPeopleAcceptEvent(Context context) { state.notPeopleAcceptEvent(this); getCurrentState(); } @Override public void payOrderEvent(Context context) { state.payOrderEvent(this); getCurrentState(); } @Override public void orderFailureEvent(Context context) { state.orderFailureEvent(this); getCurrentState(); } @Override public void feedBackEvent(Context context) { state.feedBackEvent(this); getCurrentState(); } public State getState() { return state; } public void setState(State state) { this.state = state; } @Override public String getCurrentState() { System.out.println("當前狀態 : " + state.getCurrentState()); return state.getCurrentState(); } }
package com.lin.state.project; //各種具體狀態類 public class FeedBackState extends AbstractState { @Override public String getCurrentState() { return StateEnum.FEED_BACKED.getValue(); } } class GenerateState extends AbstractState { @Override public void checkEvent(Context context) { context.setState(new ReviewState()); } @Override public void checkFailEvent(Context context) { context.setState(new FeedBackState()); } @Override public String getCurrentState() { return StateEnum.GENERATE.getValue(); } } class NotPayState extends AbstractState { @Override public void payOrderEvent(Context context) { context.setState(new PaidState()); } @Override public void feedBackEvent(Context context) { context.setState(new FeedBackState()); } @Override public String getCurrentState() { return StateEnum.NOT_PAY.getValue(); } } class PaidState extends AbstractState { @Override public void feedBackEvent(Context context) { context.setState(new FeedBackState()); } @Override public String getCurrentState() { return StateEnum.PAID.getValue(); } } class PublishState extends AbstractState { @Override public void acceptOrderEvent(Context context) { //把當前狀態設定為 NotPayState。。。 //至於應該變成哪個狀態,有流程圖來決定 context.setState(new NotPayState()); } @Override public void notPeopleAcceptEvent(Context context) { context.setState(new FeedBackState()); } @Override public String getCurrentState() { return StateEnum.PUBLISHED.getValue(); } } class ReviewState extends AbstractState { @Override public void makePriceEvent(Context context) { context.setState(new PublishState()); } @Override public String getCurrentState() { return StateEnum.REVIEWED.getValue(); } }
package com.lin.state.project; /** 測試類 */ public class Client { public static void main(String[] args) { //建立 context 物件 Context context = new Context(); //將當前狀態設定為 PublishState context.setState(new PublishState()); System.out.println(context.getCurrentState()); // //publish --> not pay context.acceptOrderEvent(context); // //not pay --> paid context.payOrderEvent(context); // // 失敗, 檢測失敗時,會丟擲異常 // try { // context.checkFailEvent(context); // System.out.println("流程正常.."); // } catch (Exception e) { // // TODO: handle exception // System.out.println(e.getMessage()); // } } }
24.1 狀態模式的注意事項和細節
1) 程式碼有很強的可讀性。狀態模式將每個狀態的行為封裝到對應的一個類中
2) 方便維護。將容易產生問題的 if-else 語句刪除了,如果把每個狀態的行為都放到一個類中,每次呼叫方法時都要判斷當前是什麼狀態,不但會產出很多 if-else 語句,而且容易出錯
3) 符合“開閉原則”。容易增刪狀態
4) 會產生很多類。每個狀態都要一個對應的類,當狀態過多時會產生很多類,加大維護難度
5) 應用場景:當一個事件或者物件有很多種狀態,狀態之間會相互轉換,對不同的狀態要求有不同的行為的時候, 可以考慮使用狀態模式
僅供參考,有錯誤還請指出!
有什麼想法,評論區留言,互相指教指教。
覺得不錯的可以點一下右邊的推薦喲!