1. 程式人生 > >《JAVA與模式》之狀態模式

《JAVA與模式》之狀態模式

在閻巨集博士的《JAVA與模式》一書中開頭是這樣描述狀態(State)模式的:

  狀態模式,又稱狀態物件模式(Pattern of Objects for States),狀態模式是物件的行為模式。

  狀態模式允許一個物件在其內部狀態改變的時候改變其行為。這個物件看上去就像是改變了它的類一樣。

狀態模式的結構

  用一句話來表述,狀態模式把所研究的物件的行為包裝在不同的狀態物件裡,每一個狀態物件都屬於一個抽象狀態類的一個子類。狀態模式的意圖是讓一個物件在其內部狀態改變的時候,其行為也隨之改變。狀態模式的示意性類圖如下所示:

  狀態模式所涉及到的角色有:

  ●  環境(Context)角色,也成上下文:定義客戶端所感興趣的介面,並且保留一個具體狀態類的例項。這個具體狀態類的例項給出此環境物件的現有狀態。

  ●  抽象狀態(State)角色:定義一個介面,用以封裝環境(Context)物件的一個特定的狀態所對應的行為。

  ●  具體狀態(ConcreteState)角色:每一個具體狀態類都實現了環境(Context)的一個狀態所對應的行為。

  原始碼

  環境角色類

複製程式碼
public class Context {
    //持有一個State型別的物件例項
    private State state;

    public void setState(State state) {
        this.state = state;
    }
    /**
     * 使用者感興趣的介面方法
     
*/ public void request(String sampleParameter) { //轉調state來處理 state.handle(sampleParameter); } }
複製程式碼

  抽象狀態類

複製程式碼
public interface State {
    /**
     * 狀態對應的處理
     */
    public void handle(String sampleParameter);
}
複製程式碼

  具體狀態類

複製程式碼
public class ConcreteStateA implements State {

    @Override
    
public void handle(String sampleParameter) { System.out.println("ConcreteStateA handle :" + sampleParameter); } }
複製程式碼複製程式碼
public class ConcreteStateB implements State {

    @Override
    public void handle(String sampleParameter) {
        
        System.out.println("ConcreteStateB handle :" + sampleParameter);
    }

}
複製程式碼

  客戶端類

複製程式碼
public class Client {

    public static void main(String[] args){
        //建立狀態
        State state = new ConcreteStateB();
        //建立環境
        Context context = new Context();
        //將狀態設定到環境中
        context.setState(state);
        //請求
        context.request("test");
    }
}
複製程式碼

  從上面可以看出,環境類Context的行為request()是委派給某一個具體狀態類的。通過使用多型性原則,可以動態改變環境類Context的屬性State的內容,使其從指向一個具體狀態類變換到指向另一個具體狀態類,從而使環境類的行為request()由不同的具體狀態類來執行。

使用場景

  考慮一個線上投票系統的應用,要實現控制同一個使用者只能投一票,如果一個使用者反覆投票,而且投票次數超過5次,則判定為惡意刷票,要取消該使用者投票的資格,當然同時也要取消他所投的票;如果一個使用者的投票次數超過8次,將進入黑名單,禁止再登入和使用系統。

  要使用狀態模式實現,首先需要把投票過程的各種狀態定義出來,根據以上描述大致分為四種狀態:正常投票、反覆投票、惡意刷票、進入黑名單。然後建立一個投票管理物件(相當於Context)。

  系統的結構圖如下所示:

  原始碼

  抽象狀態類

複製程式碼
public interface VoteState {
    /**
     * 處理狀態對應的行為
     * @param user    投票人
     * @param voteItem    投票項
     * @param voteManager    投票上下文,用來在實現狀態對應的功能處理的時候,
     *                         可以回撥上下文的資料
     */
    public void vote(String user,String voteItem,VoteManager voteManager);
}
複製程式碼

  具體狀態類——正常投票

複製程式碼
public class NormalVoteState implements VoteState {

    @Override
    public void vote(String user, String voteItem, VoteManager voteManager) {
        //正常投票,記錄到投票記錄中
        voteManager.getMapVote().put(user, voteItem);
        System.out.println("恭喜投票成功");
    }

}
複製程式碼

  具體狀態類——重複投票

複製程式碼
public class RepeatVoteState implements VoteState {

    @Override
    public void vote(String user, String voteItem, VoteManager voteManager) {
        //重複投票,暫時不做處理
        System.out.println("請不要重複投票");
    }

}
複製程式碼

  具體狀態類——惡意刷票

複製程式碼
public class SpiteVoteState implements VoteState {

    @Override
    public void vote(String user, String voteItem, VoteManager voteManager) {
        // 惡意投票,取消使用者的投票資格,並取消投票記錄
        String str = voteManager.getMapVote().get(user);
        if(str != null){
            voteManager.getMapVote().remove(user);
        }
        System.out.println("你有惡意刷屏行為,取消投票資格");
    }

}
複製程式碼

  具體狀態類——黑名單

複製程式碼
public class BlackVoteState implements VoteState {

    @Override
    public void vote(String user, String voteItem, VoteManager voteManager) {
        //記錄黑名單中,禁止登入系統
        System.out.println("進入黑名單,將禁止登入和使用本系統");
    }

}
複製程式碼

  環境類

複製程式碼
public class VoteManager {
    //持有狀體處理物件
    private VoteState state = null;
    //記錄使用者投票的結果,Map<String,String>對應Map<使用者名稱稱,投票的選項>
    private Map<String,String> mapVote = new HashMap<String,String>();
    //記錄使用者投票次數,Map<String,Integer>對應Map<使用者名稱稱,投票的次數>
    private Map<String,Integer> mapVoteCount = new HashMap<String,Integer>();
    /**
     * 獲取使用者投票結果的Map
     */
    public Map<String, String> getMapVote() {
        return mapVote;
    }
    /**
     * 投票
     * @param user    投票人
     * @param voteItem    投票的選項
     */
    public void vote(String user,String voteItem){
        //1.為該使用者增加投票次數
        //從記錄中取出該使用者已有的投票次數
        Integer oldVoteCount = mapVoteCount.get(user);
        if(oldVoteCount == null){
            oldVoteCount = 0;
        }
        oldVoteCount += 1;
        mapVoteCount.put(user, oldVoteCount);
        //2.判斷該使用者的投票型別,就相當於判斷對應的狀態
        //到底是正常投票、重複投票、惡意投票還是上黑名單的狀態
        if(oldVoteCount == 1){
            state = new NormalVoteState();
        }
        else if(oldVoteCount > 1 && oldVoteCount < 5){
            state = new RepeatVoteState();
        }
        else if(oldVoteCount >= 5 && oldVoteCount <8){
            state = new SpiteVoteState();
        }
        else if(oldVoteCount > 8){
            state = new BlackVoteState();
        }
        //然後轉調狀態物件來進行相應的操作
        state.vote(user, voteItem, this);
    }
}
複製程式碼

  客戶端類

複製程式碼
public class Client {

    public static void main(String[] args) {
        
        VoteManager vm = new VoteManager();
        for(int i=0;i<9;i++){
            vm.vote("u1","A");
        }
    }

}
複製程式碼

  執行結果如下:

  從上面的示例可以看出,狀態的轉換基本上都是內部行為,主要在狀態模式內部來維護。比如對於投票的人員,任何時候他的操作都是投票,但是投票管理物件的處理卻不一定一樣,會根據投票的次數來判斷狀態,然後根據狀態去選擇不同的處理。

認識狀態模式

  ●  狀態和行為

  所謂物件的狀態,通常指的就是物件例項的屬性的值;而行為指的就是物件的功能,再具體點說,行為大多可以對應到方法上。

  狀態模式的功能就是分離狀態的行為,通過維護狀態的變化,來呼叫不同狀態對應的不同功能。也就是說,狀態和行為是相關聯的,它們的關係可以描述為:狀態決定行為

  由於狀態是在執行期被改變的,因此行為也會在執行期根據狀態的改變而改變。

  ●  行為的平行性

注意平行線而不是平等性。所謂平行性指的是各個狀態的行為所處的層次是一樣的,相互獨立的、沒有關聯的,是根據不同的狀態來決定到底走平行線的哪一條。行為是不同的,當然對應的實現也是不同的,相互之間是不可替換的。

  而平等性強調的是可替換性,大家是同一行為的不同描述或實現,因此在同一個行為發生的時候,可以根據條件挑選任意一個實現來進行相應的處理。

  大家可能會發現狀態模式的結構和策略模式的結構完全一樣,但是,它們的目的、實現、本質卻是完全不一樣的。還有行為之間的特性也是狀態模式和策略模式一個很重要的區別,狀態模式的行為是平行性的,不可相互替換的;而策略模式的行為是平等性的,是可以相互替換的。

  ●   環境和狀態處理物件

在狀態模式中,環境(Context)是持有狀態的物件,但是環境(Context)自身並不處理跟狀態相關的行為,而是把處理狀態的功能委託給了狀態對應的狀態處理類來處理。

  在具體的狀態處理類中經常需要獲取環境(Context)自身的資料,甚至在必要的時候會回撥環境(Context)的方法,因此,通常將環境(Context)自身當作一個引數傳遞給具體的狀態處理類。

  客戶端一般只和環境(Context)互動。客戶端可以用狀態物件來配置一個環境(Context),一旦配置完畢,就不再需要和狀態物件打交道了。客戶端通常不負責執行期間狀態的維護,也不負責決定後續到底使用哪一個具體的狀態處理物件。

from: https://www.cnblogs.com/java-my-life/archive/2012/06/08/2538146.html