一、概述 狀態模式允許對象在內部狀態改變時改變它的行為,對象看起來好像修改了它自己的類 二、解決問題 它主要用來解決對象在多種狀態轉換時,需要對外輸出不同的行為的問題。狀態和行為是一一對應的,狀態之間可以相互轉換。 三、結構類圖 四、應用實例 現在很多APP都有抽獎活動,我們在這裡就用這個大家熟悉的 ...
一、概述
狀態模式允許對象在內部狀態改變時改變它的行為,對象看起來好像修改了它自己的類
二、解決問題
它主要用來解決對象在多種狀態轉換時,需要對外輸出不同的行為的問題。狀態和行為是一一對應的,狀態之間可以相互轉換。
三、結構類圖
四、應用實例
現在很多APP都有抽獎活動,我們在這裡就用這個大家熟悉的例子來講解狀態模式。假如每參加一次這個活動要扣除用戶50積分,中獎概率是10%,獎品數量固定,抽完就不能抽獎了。活動大概有下麵四種狀態,我們畫出它的狀態轉化圖。
根據上圖,我們梳理一下他們的關係,首先應該是定義出一個介面叫狀態介面,每個狀態都實現它。介面有扣除積分方法、抽獎方法、發放獎品方法,類圖如下:
有了這個類圖,我們就可以開始編碼了
package com.jet.pattern.state; /** * description: * 狀態介面 * Created by Administrator on 2017/1/15. */ public interface State { // 扣除積分 public void deductMoney(); // 是否抽中獎品 public boolean raffle(); // 發放獎品 public void dispensePrize(); }
package com.jet.pattern.state.impl; import com.jet.pattern.state.State; /** * description: * 不能抽獎狀態 * Created by Administrator on 2017/1/15. */ public class NoRaffleState implements 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.jet.pattern.state.impl; import com.jet.pattern.state.State; import sun.font.StandardTextSource; import java.util.Random; /** * description: * 可以抽獎狀態 * Created by Administrator on 2017/1/15. */ public class CanRaffleState implements 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){ // 改變活動狀態為發放獎品 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.jet.pattern.state.impl; import com.jet.pattern.state.State; /** * description: * 發放獎品狀態 * Created by Administrator on 2017/1/15. */ public class DispenseState implements 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()); } } }
package com.jet.pattern.state.impl; import com.jet.pattern.state.State; /** * description: * 獎品發放完畢狀態 * Created by Administrator on 2017/1/15. */ public class DispenseOutState implements 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.jet.pattern.state.impl; import com.jet.pattern.state.State; /** * description: * 抽獎活動 * Created by Administrator on 2017/1/15. */ public class RaffleActivity { // 活動狀態 State state = null; // 獎品數量 int count = 0; // 定義各種狀態 State noRafflleState = new NoRaffleState(this); State canRaffleState = new CanRaffleState(this); State dispenseState = new DispenseOutState(this); State dispensOutState = new DispenseOutState(this); public RaffleActivity( int count) { // 活動初始化為不能抽獎 this.state = getNoRafflleState(); this.count = count; } // 當用戶想參加抽獎時,點擊扣除積分 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; } public int getCount() { return count; } 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.jet.pattern.state.test; import com.jet.pattern.state.impl.RaffleActivity; /** * description: * 狀態模式測試類 * Created by Administrator on 2017/1/15. */ public class StateTest { public static void main(String[] args) { // 創建活動對象,獎品池有5個獎品 RaffleActivity activity = new RaffleActivity(5); // 我們連續抽三次獎 for (int i = 0; i < 3; i++) { System.out.println("--------第" + (i + 1) + "次抽獎----------"); // 參加抽獎,第一步點擊扣除積分 activity.debuctMoney(); // 第二步抽獎 activity.raffle(); } } }
測試結果:
上面例子中,我們狀態的改變是在狀態類中改變的,其實也可以在活動類中改變,這取決於代碼中那些部分是可以改動的。
五、優缺點
1、優點
(1)、代碼有很強的可讀性。狀態模式將每個狀態的行為封裝到對應的一個類中。
(2)、方便維護。將容易產生問題的if-else語句刪除了,如果把每個狀態的行為都放到一個類中,每次調用方法時都要判斷當前是什麼狀態,不但會產出很多if-else語句,而且容易出錯。
(3)、符合“開閉原則”。容易增刪狀態。
2、缺點
(1)、會產生很多類。每個狀態都要一個對應的類,當狀態過多時會產生很多類,加大維護難度。
六、使用場景
當一個事件或者對象有很多種狀態,狀態之間會相互轉換,對不同的狀態要求有不同的行為的時候使用。
七、總結
狀態模式跟策略模式很相像,其結構類圖基本一樣,我們來做一下對比。
(1)、行為是否固定。狀態模式中,在不同的狀態,其相同方法具有不同的行為。策略模式中,選擇了一個策略後,其方法也是固定的。
(2)、客戶是否需要瞭解代碼細節。狀態模式中,客戶不用管context對象的狀態時如何運作的,只要調用其提供的方法就好了。策略模式中,客戶調用context對象時,需要瞭解有哪些策略,最後選出一種策略。
(3)、使用場景。狀態模式的使用場景參看第六點。策略模式使用場景是作為繼承的一種替代方案,客戶端可以動態選擇哪一種行為,而如果沒有用策略,客戶端每當需要一種新的行為時就要創建一個子類重寫父類方法,這相當被動。