設計模式(十五)狀態模式

劉望舒發表於2019-01-22

前言

建議在閱讀本文前先閱讀設計模式(十一)策略模式這篇文章,雖說狀態模式和策略模式的結構幾乎是相同的,但是它們所解決的問題是不同的,讀完這兩篇文章你就會有了答案。

1.狀態模式定義

狀態模式定義

定義:當一個物件的內在狀態改變時允許改變其行為,這個物件看起來像是改變了其類。

狀態模式UML圖

設計模式(十五)狀態模式

在享元模式中有如下角色:

  • Context:環境角色,定義客戶端需要的介面,並且負責具體狀態的切換。
  • State:抽象狀態角色,可以是抽象類或者介面,負責物件狀態定義,並封裝了環境角色。
  • ConcreteState:具體狀態角色,實現抽象角色類,定義了本狀態所要做的事情。

2.簡單實現狀態模式

拿用mp3聽歌來說,mp3有四種基本狀態,分別是開機、關機、上一首歌和下一首歌。如果我們要寫一個對mp3進行控制的類,你可能會這樣寫,如下所示。

public class Mp3Controller {
    private static final int POWER_ON = 1;
    private static final int POWER_OFF = 2;
    private int state = POWER_OFF;
    public void powerOn() {
        if (state == POWER_OFF) {
            System.out.println("開機");
        }
        state = POWER_ON;
    }
    public void powerOff() {
        if (state == POWER_ON) {
            System.out.println("關機");
        }
        state = POWER_OFF;
    }
    public void preSong() {
        if (state == POWER_ON) {
            System.out.println("上一首歌");
        }
    }
    public void nextSong() {
        if (state == POWER_ON) {
            System.out.println("下一首歌");
        }
    }
}複製程式碼

在powerOn和powerOff方法中我們會將state置為相應的狀態,在preSong和nextSong方法中,首先要判斷當前mp3的state,如果是POWER_OFF,則不做任何處理,寫到這裡你可能會覺得實現很簡單啊。那麼我再新增些狀態,比如待機狀態、休眠狀態、亮屏狀態等等,順便再新增些功能,比如調大音量、調小音量、降噪等。這樣你實現起來,就會發現你會定義很多種狀態,在功能中可能要用到多個條件語句進行判斷,這會使得程式碼變得臃腫。
狀態模式就是為了解決這一問題,將多個條件語句去掉,使得程式碼更加清晰,下面來進行實現。

抽象狀態角色

public interface Mp3State {
    //開機
    public void powerOn();
    //關機
    public void powerOff();
    //上一首歌曲
    public void preSong();
    //下一首歌曲
    public void nextSong();
}複製程式碼

介面Mp3State中定義了四種功能,接下來我們來實現Mp3State。

具體狀態角色

我們先來實現開機狀態,程式碼如下所示。

public class PowerOnState implements Mp3State {
    @Override
    public void powerOn() {
        System.out.println("已開機");
    }
    @Override
    public void powerOff() {
        System.out.println("關機");
    }
    @Override
    public void preSong() {
        System.out.println("上一首歌");
    }
    @Override
    public void nextSong() {
        System.out.println("下一首歌");
    }
}複製程式碼

比較特殊的是powerOn方法中,列印了“已開機”,因為在PowerOnState 狀態下進行開機操作是多此一舉的。
接著實現關機狀態:

public class PowerOffState implements Mp3State {
    @Override
    public void powerOn() {
        System.out.println("開機");
    }
    @Override
    public void powerOff() {
    }
    @Override
    public void preSong() {
    }
    @Override
    public void nextSong() {
    }
}複製程式碼

在關機狀態中只實現了powerOn方法,其他的方法都是空實現。

環境角色

public class Context {
    private Mp3State mp3State;
    public void setMp3State(Mp3State mp3State){
        this.mp3State=mp3State;
    }
    public void powerOn(){
        mp3State.powerOn();
        setMp3State(new PowerOnState());
    }
    public void powerOff(){
        mp3State.powerOff();
        setMp3State(new PowerOffState());
    }
    public void preSong(){
        mp3State.preSong();
    }
    public void nextSong(){
        mp3State.nextSong();
    }
}複製程式碼

Context 中定義了setMp3State方法,用來設定狀態,其中powerOn方法中會呼叫setMp3State方法將狀態置為PowerOffState,同理powerOff中將狀態置為PowerOffState。

客戶端呼叫

public class Client {
    public static void main(String[] args){
        Context context=new Context();
        context.setMp3State(new PowerOffState());
        context.preSong();
        context.powerOn();
        context.nextSong();
        context.powerOff();
    }
}複製程式碼

我們只需要先設定mp3的初始狀態,就可以呼叫各種功能方法了,不需要再考慮功能和狀態之間的關係。輸出結果為:
開機
下一首歌
關機

雖然這個例子的程式碼很簡單,這裡還是給出UML圖,如下所示。

設計模式(十五)狀態模式

3.狀態模式的使用場景和優缺點

優點

  • 避免了過多的條件語句,使得結構更清晰,提高程式碼的可維護性。
  • 每個狀態都是一個子類,方便增加和修改狀態。
  • 狀態被放置到類的內部,外部呼叫不需要知道類的內部如何實現狀態和行為的變換。

缺點

  • 完全使用狀態模式,可能會導致子類會過多。

使用場景

  • 程式碼中包含大量與物件狀態有關的條件語句。
  • 物件的行為依賴著狀態,並且行為隨著狀態的改變而改變。

github原始碼


歡迎關注我的微信公眾號,第一時間獲得部落格更新提醒,以及更多成體系的Android相關原創技術乾貨。
掃一掃下方二維碼或者長按識別二維碼,即可關注。

設計模式(十五)狀態模式

相關文章