基本介紹
狀態模式(State Pattern)主要用來解決物件在多種狀態轉換時,需要對外輸出不同的行為的問題,類的行為是基於它的狀態改變的。
模式結構
Context(環境角色):用於維護 State 例項,這個例項定義當前狀態
State(抽象狀態):定義一個介面以封裝與 Context 的一個特定狀態相關的行為
ConcreteState(具體狀態):每一子類實現一個與 Context 的一個狀態相關的行為
舉例說明
我們以抽獎的場景作為例子,流程如下:
定義一個抽象狀態:State
public interface State {
RuntimeException EXCEPTION = new RuntimeException("不能執行此操作");
/**
* 報名
*/
default void signUp() {
throw EXCEPTION;
}
/**
* 抽獎
*/
default boolean raffle() {
throw EXCEPTION;
}
/**
* 發獎品
*/
default void sendPrize() {
throw EXCEPTION;
}
}
具體狀態1:不能抽獎
public class NotRaffleState implements State {
private RaffleActivity activity;
public NotRaffleState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public void signUp() {
//如果沒有獎品,直接進入無獎品狀態
if (!activity.hasPrize()) {
activity.setState(activity.getNoPrizeState());
return;
}
System.out.println("報名成功,可以抽獎了");
activity.setState(activity.getCanRaffleState());
}
}
具體狀態2:可以抽獎
public class CanRaffleState implements State {
private RaffleActivity activity;
public CanRaffleState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public boolean raffle() {
//模擬五分之一的中獎機率
int n = new Random().nextInt(5);
if (n == 0) {
//抽中,進入發放獎品的狀態
System.out.println("恭喜你,抽中了");
activity.setState(activity.getSendPrizeState());
return true;
} else {
//未抽中,回到初始狀態
System.out.println("很遺憾,沒有抽中");
activity.setState(activity.getNotRaffleState());
return false;
}
}
}
具體狀態3:發放獎品
public class SendPrizeState implements State {
private RaffleActivity activity;
public SendPrizeState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public void sendPrize() {
int count = activity.getCount();
//沒有獎品了
if (count <= 0) {
System.out.println("獎品沒有了,下次再來吧");
activity.setState(activity.getNoPrizeState());
} else {
activity.setCount(--count);
System.out.println("獎品已傳送");
activity.setState(activity.getNotRaffleState());
}
}
}
具體狀態4:獎品領完
public class NoPrizeState implements State {
private RaffleActivity activity;
public NoPrizeState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public void signUp() {
System.out.println("獎品沒有了,下次再來吧");
System.exit(0);
}
@Override
public boolean raffle() {
System.out.println("獎品沒有了,下次再來吧");
System.exit(0);
return false;
}
@Override
public void sendPrize() {
System.out.println("獎品沒有了,下次再來吧");
System.exit(0);
}
}
環境角色:抽獎活動
public class RaffleActivity {
private State state;
private int count;
private State notRaffleState = new NotRaffleState(this);
private State canRaffleState = new CanRaffleState(this);
private State sendPrizeState = new SendPrizeState(this);
private State noPrizeState = new NoPrizeState(this);
public RaffleActivity(int count) {
this.state = getNotRaffleState();
this.count = count;
}
public void signUp() {
state.signUp();
}
public void raffle() {
if (state.raffle()) {
state.sendPrize();
}
}
public State getState() {
return state;
}
public boolean hasPrize(){
return count > 0;
}
//省略getter、setter
}
客戶端
public class Client {
@Test
public void test(){
RaffleActivity activity = new RaffleActivity(1);
for (int i = 0; i < 5; i++) {
System.out.println("第" + (i + 1) + "次抽獎");
activity.signUp();
activity.raffle();
System.out.println("------------------");
}
}
}
執行結果
第1次抽獎
報名成功,可以抽獎了
很遺憾,沒有抽中
------------------
第2次抽獎
報名成功,可以抽獎了
恭喜你,抽中了
獎品已傳送
------------------
第3次抽獎
獎品沒有了,下次再來吧
模式分析
優點:
- 程式碼具有較強的可讀性。狀態模式將每個狀態的行為封裝到對應的一個類中
- 方便維護。將容易產生問題的 if-else 語句刪除了,如果把每個狀態的行為都放到一個類中,每次呼叫方法時都要判斷當前是什麼狀態,不但會產出很多 if-else 語句,而且容易出錯
缺點:
- 會產生很多類。每個狀態都對應一個類,當狀態過多時,維護難度變大
- 狀態模式的結構與實現都較為複雜,如果使用不當將導致程式結構和程式碼的混亂
- 狀態模式對"開閉原則"的支援並不太好,對於可以切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的原始碼,否則無法切換到新增狀態,而且修改某個狀態類的行為也需修改對應類的原始碼。
適用環境:
-
行為隨狀態改變而改變的場景
-
條件、分支語句的代替者