行為型設計模式 - 狀態模式詳解

農夫三拳有點疼~發表於2020-05-15

基本介紹

狀態模式(State Pattern)主要用來解決物件在多種狀態轉換時,需要對外輸出不同的行為的問題,類的行為是基於它的狀態改變的。

模式結構

Context(環境角色):用於維護 State 例項,這個例項定義當前狀態

State(抽象狀態):定義一個介面以封裝與 Context 的一個特定狀態相關的行為

ConcreteState(具體狀態):每一子類實現一個與 Context 的一個狀態相關的行為

舉例說明

我們以抽獎的場景作為例子,流程如下:

定義一個抽象狀態:State

public interface State {
    //報名
    void signUp();

    //抽獎
    boolean raffle();

    //發獎品
    void sendPrize();
}

具體狀態1:不能抽獎

public class NotRaffleState implements State {

    private RaffleActivity activity;

    public NotRaffleState(RaffleActivity activity) {
        this.activity = activity;
    }

    @Override
    public void signUp() {
        if(activity.getCount() <= 0){
            System.out.println("獎品沒有了,下次再來吧");
            activity.setState(activity.getNoPrizeState());
            return;
        }
        System.out.println("報名成功,可以抽獎了");
        activity.setState(activity.getCanRaffleState());
    }

    @Override
    public boolean raffle() {
        System.out.println("還沒有報名");
        return false;
    }

    @Override
    public void sendPrize() {
        System.out.println("還沒有報名");
    }
}

具體狀態2:可以抽獎

public class CanRaffleState implements State {

    private RaffleActivity activity;

    public CanRaffleState(RaffleActivity activity) {
        this.activity = activity;
    }

    @Override
    public void signUp() {
        System.out.println("已經報過名了");
    }

    @Override
    public boolean raffle() {
        if(activity.getCount() <= 0){
            System.out.println("獎品沒有了,下次再來吧");
            activity.setState(activity.getNoPrizeState());
            return false;
        }
        //模擬五分之一的中獎機率
        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;
        }
    }

    @Override
    public void sendPrize() {
        System.out.println("還未進行抽獎");
    }
}

具體狀態3:發放獎品

public class SendPrizeState implements State {

    private RaffleActivity activity;

    public SendPrizeState(RaffleActivity activity) {
        this.activity = activity;
    }

    @Override
    public void signUp() {
        System.out.println("已經報過名了");
    }

    @Override
    public boolean raffle() {
        System.out.println("已經參與抽獎了");
        return false;
    }

    @Override
    public void sendPrize() {
        int count = activity.getCount();
        //沒有獎品了
        if (count <= 0) {
            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("獎品沒有了,下次再來吧");
    }

    @Override
    public boolean raffle() {
        System.out.println("獎品沒有了,下次再來吧");
        return false;
    }

    @Override
    public void sendPrize() {
        System.out.println("獎品沒有了,下次再來吧");
    }
}

環境角色:抽獎活動

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();
        }
    }
    //省略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次抽獎
獎品沒有了,下次再來吧
獎品沒有了,下次再來吧
------------------
第4次抽獎
獎品沒有了,下次再來吧
獎品沒有了,下次再來吧
------------------
第5次抽獎
獎品沒有了,下次再來吧
獎品沒有了,下次再來吧
------------------

模式分析

優點:

  • 程式碼具有較強的可讀性。狀態模式將每個狀態的行為封裝到對應的一個類中
  • 方便維護。將容易產生問題的 if-else 語句刪除了,如果把每個狀態的行為都放到一個類中,每次呼叫方法時都要判斷當前是什麼狀態,不但會產出很多 if-else 語句,而且容易出錯

缺點:

  • 會產生很多類。每個狀態都對應一個類,當狀態過多時,維護難度變大
  • 狀態模式的結構與實現都較為複雜,如果使用不當將導致程式結構和程式碼的混亂
  • 狀態模式對"開閉原則"的支援並不太好,對於可以切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的原始碼,否則無法切換到新增狀態,而且修改某個狀態類的行為也需修改對應類的原始碼。

適用環境:

  • 行為隨狀態改變而改變的場景

  • 條件、分支語句的代替者

相關文章