狀態機模式


現在需要你做一個簡單是視頻播放器的APP,主要有播放,暫停,停止三個功能,在沒學狀態機模式之前,你可能會這樣來實現:

現抽象個IPlayer接口,定義好你的播放器需要實現的動作和可能的狀態字段:

 1 public interface IPlayer {
 2     public static final int STATE_PLAYING = 1;
 3     public static final int STATE_PAUSED = 2;
 4     public static final int STATE_STOPPED = 3;
 5 
 6     public void palyVedio();
 7 
 8     public void pause();
 9 
10     public void stop();
11 }
IPlayer

現在就可以實現IPlayer接口了:

 1 public class VedioPlayer implements IPlayer {
 2     public int mCurrentState;
 3 
 4     @Override
 5     public void palyVedio() {
 6         switch (mCurrentState) {
 7         case STATE_PLAYING:
 8             System.out.println(" curent state is palying, do nothing.");
 9         case STATE_PAUSED:
10         case STATE_STOPPED:
11             System.out.println("paly vedio now.");
12             break;
13         default:
14             // would it happen? who care.
15             break;
16         }
17         mCurrentState = STATE_PLAYING;
18     }
19 
20     @Override
21     public void pause() {
22         switch (mCurrentState) {
23         case STATE_PLAYING:
24             System.out.println("pause vedio now");
25             break;
26         case STATE_PAUSED:
27             System.out.println(" curent state is paused, do noting.");
28         case STATE_STOPPED:
29             System.out.println("curent state is stopped,do noting.");
30             break;
31         default:
32             // would it happen? who care.
33             break;
34         }
35         mCurrentState = STATE_PAUSED;
36     }
37 
38     @Override
39     public void stop() {
40         switch (mCurrentState) {
41         case STATE_PLAYING:
42         case STATE_PAUSED:
43             System.out.println(" stop vedio now.");
44         case STATE_STOPPED:
45             System.out.println("curent state is stopped,do noting.");
46             break;
47         default:
48             // would it happen? who care.
49             break;
50         }
51         mCurrentState = STATE_STOPPED;
52     }
53 
54 
55 }

 

看着還錯喔。

我們都知道,需求總是會改變的,現在你的boss需要在視頻播放中(片頭或者片尾什么的)可以播放一段廣告。嗯,你可能會覺得沒關系,只需要在接口上增加多一個方法就好了,同時增加個狀態字段,修改后:

 1 public interface IPlayer {
 2     public static final int STATE_PLAYING = 1;
 3     public static final int STATE_PAUSED = 2;
 4     public static final int STATE_STOPPED = 3;
 5     public static final int STATE_AD = 4;
 6     
 7     public void palyVedio();
 8     public void pause();
 9     public void stop();
10     public void showAD();
11 }
IPlayer

最后你認為只需要VedioPlayer實現增加的showAD方法就大功告成了,

 1     @Override
 2     public void showAD() {
 3         switch (mCurrentState) {
 4         case STATE_AD:
 5             System.out.println("curent state is AD,do noting");
 6             break;
 7         case STATE_PLAYING:
 8             System.out.println("show advertisement now.");
 9             break;
10         case STATE_PAUSED:
11             System.out.println("curent state is paused , do noting");
12         case STATE_STOPPED:
13             System.out.println("curent state is stopped ,do noting.");
14             break;
15         default:
16             // would it happen? who care.
17             break;
18         }
19         mCurrentState = STATE_AD;
20     }

真的就完了?終於發現了,palyVedio,pause,stop三個方法中的swtich里面還需要各多加一個case的判斷,納尼!!!如果以后又增加幾個狀態,那么還得修改啊,而且隨着狀態的增加,修改的代碼也會成倍的增加,簡直不可想象。這種情況下,狀態機模式就可以幫你個大忙了。

狀態機模式:允許對象在內部狀態改變時改變它的行為,對象看起來就好像修改了它的類。

看着還是有點抽象吧,這里的Context就相當於我們的VedioPlayer類,我們繼續以視頻播放為例子:

首先還是實現播放,暫停,停止狀態,此時的狀態轉換圖應該是這樣:


 

 

 

 

 

 

 

 

 

 

 

 

還是先抽象一個IPlayer作為上下文(Context):

 1 public abstract class IPlayer {
 2     
 3     public abstract void request(int flag);
 4     
 5     public abstract void setState(PlayerState state);
 6     
 7     public abstract void palyVedio();
 8 
 9     public abstract void pause();
10 
11     public abstract void stop();
12 
13     public abstract void showAD();
14 }

可以看到有一個setState方法,這是為了可以設置內部狀態。

有了Context,我來實現State吧,這里寫成一個抽線類

 1 public abstract class PlayerState {
 2     public final static int PLAY_OR_PAUSE=0;
 3     public final static int STOP=1;
 4     protected IPlayer mPlayer;
 5     public PlayerState(IPlayer player) {
 6         this.mPlayer=player;
 7     }
 8     public abstract void handle(int action);
 9     @Override
10     public String toString() {
11         return "current state:"+this.getClass().getSimpleName();
12     }
13 }

 

再看State的實現,我們有播放,暫停,停止三種狀態,所以需要三個實現類:

public class PlayingState extends PlayerState {
    public PlayingState(IPlayer player) {
        super(player);
    }

    @Override
    public void handle(int action) {
        switch (action) {
        case PlayingState.PLAY_OR_PAUSE:
            mPlayer.pause();
            mPlayer.setState(new PausedState(mPlayer));
            break;
        case PlayerState.STOP:
            mPlayer.stop();
            mPlayer.setState(new StoppedState(mPlayer));
            break;
        default:
            throw new IllegalArgumentException("ERROE ACTION:"+action+",current state:"+this.getClass().getSimpleName());
        }
    }
}
PlayingState
public class PausedState extends PlayerState {

    public PausedState(IPlayer player) {
        super(player);
    }
    @Override
    public void handle(int action) {
        switch (action) {
        case PlayingState.PLAY_OR_PAUSE:
            mPlayer.palyVedio();
            mPlayer.setState(new PlayingState(mPlayer));
            break;
        case PlayerState.STOP:
            mPlayer.stop();
            mPlayer.setState(new StoppedState(mPlayer));
            break;
        default:
            throw new IllegalArgumentException("ERROE ACTION:"+action+",current state:"+this.getClass().getSimpleName());
        }
    }
}
PausedState
public class StoppedState extends PlayerState {

    public StoppedState(IPlayer player) {
        super(player);
    }

    @Override
    public void handle(int action) {
        switch (action) {
        case PlayingState.PLAY_OR_PAUSE:
            mPlayer.palyVedio();
            mPlayer.setState(new PlayingState(mPlayer));
            break;
        default:
            throw new IllegalArgumentException("ERROE ACTION:"+action+",current state:"+this.getClass().getSimpleName());
        }
    }
}
StoppedState

最后就是IPlayer的實現類VedioPlayer

public class VedioPlayer extends IPlayer {
    private PlayerState mState=new StoppedState(this);

    @Override
    public void palyVedio() {
        System.out.println("play vedio!");
    }

    @Override
    public void pause() {
        System.out.println("pause vedio!");
    }

    @Override
    public void stop() {
        System.out.println("stop vedio!");
    }

    // @Override
    // public void showAD() {
    // System.out.println("show AD!");
    // }

    @Override
    public void setState(PlayerState state) {
        mState = state;
    }

    @Override
    public void request(int action) {
        System.out.println("before action:" + mState.toString());
        mState.handle(action);
        System.out.println("after action:" + mState.toString());
    }

}

現在的代碼就簡潔多了,因為VedioPlayer只需要實現需要的操作,每次接收輸入的時候(request方法調用),只需要交給當前的狀態去處理,而每個狀態不需要知道自己之前的狀態是什么,只需要知道接收到什么樣的輸入而做出相應的操作和下一個狀態,現在來驗證下正確性:

 1 public class Main {
 2 
 3     /**
 4      * @param args
 5      */
 6     public static void main(String[] args) {
 7         Scanner sc=new Scanner(System.in);
 8         IPlayer player=new VedioPlayer();
 9         int i=-1;
10         while((i=sc.nextInt())!=-1){
11             player.request(i);
12         }
13     }
14 
15 }

依次如下輸入:

 

最后拋出了java.lang.IllegalArgumentException: ERROE ACTION:1,current state:StoppedState,因為在stopped狀態下,又再次嘗試stop,具體可以看StoppedState的實現。從流程來看,也驗證了程序的正確性。

現在我們為視頻播放器添加一個播放廣告的狀態,此時系統的狀態:

  上面我們提到VedioPlayer只需要實現需要的操作,每次接收輸入的時候(request方法調用),只需要交給當前的狀態去處理。

  也就是說現在的VedioPlayer再實現一個showAD的操作就可以了,剩下的就是狀態們之間的事了。

@Override
    public void showAD() {
        System.out.println("show AD!");
    }

  現在增加一個ADState

public class ShowADState extends PlayerState {
    public ShowADState(IPlayer player) {
        super(player);
    }
    @Override
    public void handle(int action) {
        switch (action) {
        case PlayingState.PLAY_OR_PAUSE:
            mPlayer.palyVedio();
            mPlayer.setState(new PlayingState(mPlayer));
            break;
        default:
            throw new IllegalArgumentException("ERROE ACTION:"+action+","+this.toString());
        }
    }

}

現在依然還沒有完事,前面提到,每個狀態不需要知道自己之前的狀態是什么,只需要知道接收到什么樣的輸入而做出相應的操作和下一個狀態。

由狀態圖可以看到,PlayingState的下一個狀態增加了一個ShowADState,所以PlayingState還需要做一點修改,如下:

 

 1 public class PlayingState extends PlayerState {
 2     public PlayingState(IPlayer player) {
 3         super(player);
 4     }
 5 
 6     @Override
 7     public void handle(int action) {
 8         switch (action) {
 9         case PlayingState.PLAY_OR_PAUSE:
10             mPlayer.pause();
11             mPlayer.setState(new PausedState(mPlayer));
12             break;
13         case PlayerState.STOP:
14             mPlayer.stop();
15             mPlayer.setState(new StoppedState(mPlayer));
16             break;
17         case PlayingState.SHOW_AD:
18             mPlayer.showAD();
19             mPlayer.setState(new ShowADState(mPlayer));
20             break;
21         default:
22             throw new IllegalArgumentException("ERROE ACTION:"+action+",current state:"+this.getClass().getSimpleName());
23         }
24     }
25 }

增加了17到20行的代碼。

再來驗證程序:

同樣可以正確的運行。也可以看出,對於狀態的增加,所帶來的修改成本比沒用狀態機模式要小的多,特別對於狀態更多的程序。

至此狀態機模式也講完了。

總結:

1.狀態機模式:允許對象在內部狀態改變時改變它的行為,對象看起來就好像修改了它的類(每個狀態可以做出不一樣的動作);

2.擁有多個狀態的對象(Context)只需要實現需要的操作,每次接收輸入的時候(request方法調用),只需要交給當前的狀態去處理,而每個狀態不需要知道自己之前的狀態是什么,只需要知道接收到什么樣的輸入(或者沒輸入)而做出相應的操作和自己下一個狀態是什么即可;

3.適當的畫出系統的狀態轉換圖,可以更清晰地實現系統狀態機。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM