一. 概述
- 狀態模式(State Pattern):它主要用來解決對象在多種狀態轉換時,需要對外輸出不同的行爲的問題。狀態和行爲是一一對應的,狀態之間可以相互轉換。
- 當一個對象的內在狀態改變時,允許改變其行爲,這個對象看起來像是改變了其類。
- 原理類圖:
- Context 類爲環境角色, 用於維護State實例,這個實例定義當前狀態
- State 是抽象狀態角色,定義一個接口封裝與Context 的一個特點接口相關行爲
- ConcreteState 具體的狀態角色,每個子類實現一個與Context 的一個狀態相關行爲
二. 場景示例
- APP抽獎活動問題
編寫程序完成APP抽獎活動 具體要求如下:
- 假如每參加一次這個活動要扣除用戶50積分,中獎概率是10%
- 獎品數量固定,抽完就不能抽獎
- 活動有四個狀態: 可以抽獎、不能抽獎、發放獎品和獎品領完
- 活動的四個狀態轉換關係圖:
-
思路分析類圖:
-
定義出一個接口叫狀態接口,每個狀態都實現它。
-
接口有扣除積分方法、抽獎方法、發放獎品
-
代碼實現:
/**
* 狀態抽象類
*/
public abstract class State {
// 扣除積分 - 50
public abstract void deductMoney();
// 是否抽中獎品
public abstract boolean raffle();
// 發放獎品
public abstract void dispensePrize();
}
/**
* 抽獎活動
*/
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;
}
}
/**
* 可以抽獎的狀態
*/
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("沒中獎,不能發放獎品");
}
}
/**
* 獎品發放完畢狀態
* 說明,當我們activity 改變成 DispenseOutState, 抽獎活動結束
*/
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("獎品發送完了,請下次再參加");
}
}
/**
* 發放獎品的狀態
*/
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);
}
}
}
/**
* 不能抽獎狀態
*/
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("不能發放獎品");
}
}
/**
* 狀態模式測試類
*/
public class ClientTest {
public static void main(String[] args) {
// 創建活動對象,獎品有1個獎品
RaffleActivity activity = new RaffleActivity(1);
// 我們連續抽300次獎
for (int i = 0; i < 30; i++) {
System.out.println("--------第" + (i + 1) + "次抽獎----------");
// 參加抽獎,第一步點擊扣除積分
activity.debuctMoney();
// 第二步抽獎
activity.raffle();
}
}
}
三. 狀態模式在實際項目-借貸平臺中的應用
- 借貸平臺的訂單,有審覈-發佈-搶單 等等 步驟,隨着操作的不同,會改變訂單的狀態, 項目中的這個模塊實現就會使用到狀態模式
- 通常通過if/else判斷訂單的狀態,從而實現不同的邏輯,僞代碼如下
- 使用狀態模式完成 借貸平臺項目的審覈模塊
/**
* 狀態接口
*/
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();
}
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;
}
}
//環境上下文
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();
}
}
/**
* 狀態枚舉類
*/
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;}
}
//各種具體狀態類
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();
}
}
/**測試類*/
public class ClientTest {
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());
// }
}
}
四. 狀態模式的注意事項和細節
- 代碼有很強的可讀性。狀態模式將每個狀態的行爲封裝到對應的一個類中
- 方便維護。將容易產生問題的if-else語句刪除了,如果把每個狀態的行爲都放到一個類中,每次調用方法時都要判斷當前是什麼狀態,不但會產出很多if-else語句,而且容易出錯
- 符合“開閉原則”。容易增刪狀態
- 會產生很多類。每個狀態都要一個對應的類,當狀態過多時會產生很多類,加大維護難度
- 應用場景:當一個事件或者對象有很多種狀態,狀態之間會相互轉換,對不同的狀態要求有不同的行爲的時候,可以考慮使用狀態模式