JAVA設計模式:狀態模式


 聲明:轉載請說明來源:http://www.cnblogs.com/pony1223/p/7518226.html

一、引出狀態模式

假設我們現在有一個糖果機項目,那么我們知道正常一般糖果機提供給用戶的行為有這么幾種:投入硬幣、轉動曲柄、退出硬幣幾種行為;那么糖果機呢一般有這幾中狀態,待機狀態、持有硬幣的准備狀態、運行狀態即正在售出狀態和初始狀態 這么幾種正常狀態。 我們發現處於不同狀態的時候,持有的行為是不一樣的,圖如下:

 

 

如果我們采用傳統的方法來寫代碼,那么在投入硬幣這個行為操作的時候,我們會進行狀態的判斷,只有在處於待機狀態情況下這種行為是正常的,而其他則非正常,那么其他行為也一樣,都需要去先判斷下當前的狀態來進行操作。得到的代碼則為:

  1 package study.designmode.statemode;
  2 
  3 public class CandyMachine {
  4 
  5     final static int SoldOutState = 0; //初始狀態
  6     final static int OnReadyState = 1;  //待機狀態
  7     final static int HasCoin = 2;  //准備狀態
  8     final static int SoldState = 3;  //售出狀態
  9 
 10     private int state = SoldOutState; //變量,用於存放當前的狀態值
 11     private int count = 0; //糖果的數目
 12 
 13     public CandyMachine(int count) {
 14         this.count = count;
 15         if (count > 0) {
 16             state = OnReadyState;
 17         }
 18     }
 19 
 20     //投入硬幣行為的時候,通過判斷當前的狀態來匹配所有的狀態.
 21     public void insertCoin() {
 22         switch (state) {
 23         case SoldOutState:
 24             System.out.println("you can't insert coin,the machine sold out!");
 25             break;
 26         case OnReadyState: //只有在待機狀態的時候,投入硬幣行為正確,並將狀態改變為准備狀態
 27             state = HasCoin;
 28             System.out
 29                     .println("you have inserted a coin,next,please turn crank!");
 30             break;
 31         case HasCoin:
 32             System.out.println("you can't insert another coin!");
 33 
 34             break;
 35         case SoldState:
 36             System.out.println("please wait!we are giving you a candy!");
 37 
 38             break;
 39         }
 40 
 41     }
 42 
 43     //回退硬幣
 44     public void returnCoin() {
 45         switch (state) {
 46         case SoldOutState:
 47             System.out
 48                     .println("you can't return,you haven't inserted a coin yet!");
 49             break;
 50         case OnReadyState:
 51             System.out.println("you haven't inserted a coin yet!");
 52             break;
 53         case HasCoin:
 54 
 55             System.out.println("coin return!");
 56             state = OnReadyState;
 57 
 58             break;
 59         case SoldState:
 60             System.out.println("sorry,you already have turned the crank!");
 61 
 62             break;
 63         }
 64 
 65     }
 66 
 67     //轉動曲柄
 68     public void turnCrank() {
 69         switch (state) {
 70         case SoldOutState:
 71             System.out.println("you turned,but there are no candies!");
 72             break;
 73         case OnReadyState:
 74             System.out.println("you turned,but you haven't inserted a coin!");
 75             break;
 76         case HasCoin:
 77             System.out.println("crank turn...!");
 78             state = SoldState;
 79             dispense();
 80             break;
 81         case SoldState:
 82             System.out
 83                     .println("we are giving you a candy,turning another get nothing,!");
 84             break;
 85         }
 86 
 87     }
 88 
 89     //觸發發放糖果行為
 90     private void dispense() {
 91         count = count - 1;
 92         System.out.println("a candy rolling out!");
 93         if (count > 0) {
 94             state = OnReadyState;
 95         } else {
 96             System.out.println("Oo,out of candies");
 97             state = SoldOutState;
 98         }
 99 
100     }
101 
102     public void printstate() {
103 
104         switch (state) {
105         case SoldOutState:
106             System.out.println("***SoldOutState***");
107             break;
108         case OnReadyState:
109             System.out.println("***OnReadyState***");
110             break;
111         case HasCoin:
112 
113             System.out.println("***HasCoin***");
114 
115             break;
116         case SoldState:
117             System.out.println("***SoldState***");
118             break;
119         }
120 
121     }
122 }

那么上面這種方式存在什么問題呢?首先很直觀的感受就是:

1.存在大量的switch case 語句  當然可以用if  else 也是一樣的。

 

2.可擴展性差,並且一旦要加入一種新的狀態,那么就會要修改所有的switch case  不符合開閉原則

3.沒有采用面向對象的方式去封裝

比如,這個時候,新增加了一種狀態,贏家狀態,即可以獲取到兩粒糖果;那么如果用上面的方式,肯定是不符合開閉原則的,同時擴展性也是不好的;那么我們有什么其它的方式來解決呢?

 

二、解決辦法

為了解決上面的問題,我們首先分析項目中變化的部分和不變的部分,抽化出變化的部分,我們發現糖果機提供的行為一般是不變的,就是投入硬幣、轉動曲柄給、退回硬幣、機器發放糖果;而糖果機的狀態是可以變化的,可以新增出一種狀態來,比如我們說的贏家狀態。那么我們這個抽出變化的部分,即我們說的狀態,於是出現了下面的結構設計方案:

 

這個結構圖告訴我們,提煉出狀態接口出來,然后將各個狀態抽出,並去實現接口,每個狀態都持有投入硬幣,退回硬幣,轉動曲柄、售出糖果這幾種行為對應的方法做出相應;而糖果機持有所有的狀態,並通過引用狀態接口來操作各個狀態;這種設計架構就是我們說的狀態模式。

狀態模式定義:對象行為的變化是由於狀態的變化引入,那么即當內部狀態發生變化的時候,就會改變對象的行為,而這種改變視乎就改變了整個類。

那么現在采用狀態模式來解決問題:

1.首先定義接口:

 

package study.designmode.statemode.state;

public interface State {
    public void insertCoin();
    public void returnCoin();
    public void turnCrank();
    public void dispense();
    public void printstate();
}

 

2.定義各個狀態的實現類

准備狀態:

package study.designmode.statemode.state;

import java.util.Random;

public class HasCoin implements State {
    private CandyMachine mCandyMachine;

    public HasCoin(CandyMachine mCandyMachine) {
        this.mCandyMachine = mCandyMachine;
    }

    @Override
    public void insertCoin() {
        // TODO Auto-generated method stub
        System.out.println("you can't insert another coin!");

    }

    @Override
    public void returnCoin() {
        // TODO Auto-generated method stub
        System.out.println("coin return!");
        mCandyMachine.setState(mCandyMachine.mOnReadyState);
    }

    @Override
    public void turnCrank() {
        // TODO Auto-generated method stub
        System.out.println("crank turn...!");
        Random ranwinner=new Random();
        int winner=ranwinner.nextInt(10);
        if(winner==0)
        {
            mCandyMachine.setState(mCandyMachine.mWinnerState);

        }else
        {
            mCandyMachine.setState(mCandyMachine.mSoldState);

        }
        
    }

    @Override
    public void dispense() {
    }

    @Override
    public void printstate() {
        // TODO Auto-generated method stub
        System.out.println("***HasCoin***");

    }

}

 

說明:我們會發現里面存在一個糖果機的屬性,而之所以存在這個屬性,就是因為糖果機中持有所有的狀態,而在准備狀態下,肯定會由於某種行為發生狀態改變,而要改變的狀態都在糖果機中,所以持有一個糖果機屬性,下面也一樣,不在重復說明。

准備狀態:

package study.designmode.statemode.state;

public class OnReadyState implements State {
    private CandyMachine mCandyMachine;
    public OnReadyState(CandyMachine mCandyMachine)
    {
        this.mCandyMachine=mCandyMachine;
    }

    @Override
    public void insertCoin() {
        // TODO Auto-generated method stub
        System.out
        .println("you have inserted a coin,next,please turn crank!");
        mCandyMachine.setState(mCandyMachine.mHasCoin);
    }

    @Override
    public void returnCoin() {
        // TODO Auto-generated method stub
        System.out.println("you haven't inserted a coin yet!");
        
    }

    @Override
    public void turnCrank() {
        // TODO Auto-generated method stub
        System.out.println("you turned,but you haven't inserted a coin!");
        
    }

    @Override
    public void dispense() {
        // TODO Auto-generated method stub

    }

    @Override
    public void printstate() {
        // TODO Auto-generated method stub
        System.out.println("***OnReadyState***");
        
    }

}

 

初始狀態:

package study.designmode.statemode.state;

public class SoldOutState implements State {

    private CandyMachine mCandyMachine;
    public SoldOutState(CandyMachine mCandyMachine)
    {
        this.mCandyMachine=mCandyMachine;
    }

    @Override
    public void insertCoin() {
        // TODO Auto-generated method stub
        System.out.println("you can't insert coin,the machine sold out!");
        
    }

    @Override
    public void returnCoin() {
        // TODO Auto-generated method stub
        System.out
        .println("you can't return,you haven't inserted a coin yet!");

    }

    @Override
    public void turnCrank() {
        // TODO Auto-generated method stub
        System.out.println("you turned,but there are no candies!");
        
    }

    @Override
    public void dispense() {
        // TODO Auto-generated method stub

    }

    @Override
    public void printstate() {
        // TODO Auto-generated method stub
        System.out.println("***SoldOutState***");
    
    }

}

 

售出狀態:

 

package study.designmode.statemode.state;

public class SoldState implements State {
    private CandyMachine mCandyMachine;
    public SoldState(CandyMachine mCandyMachine)
    {
        this.mCandyMachine=mCandyMachine;
    }

    @Override
    public void insertCoin() {
        // TODO Auto-generated method stub
        System.out.println("please wait!we are giving you a candy!");

    }

    @Override
    public void returnCoin() {
        // TODO Auto-generated method stub
        System.out.println("you haven't inserted a coin yet!");
        
    }

    @Override
    public void turnCrank() {
        // TODO Auto-generated method stub
        System.out
        .println("we are giving you a candy,turning another get nothing,!");

    }

    @Override
    public void dispense() {
        // TODO Auto-generated method stub
        
        mCandyMachine.releaseCandy();
        if (mCandyMachine.getCount() > 0) {
            mCandyMachine.setState(mCandyMachine.mOnReadyState);
        } else {
            System.out.println("Oo,out of candies");
            mCandyMachine.setState(mCandyMachine.mSoldOutState);
        }

    
    
    }

    @Override
    public void printstate() {
        // TODO Auto-generated method stub
        System.out.println("***SoldState***");
        
    }

}

 

贏家狀態:

package study.designmode.statemode.state;

public class WinnerState implements State {

    private CandyMachine mCandyMachine;

    public WinnerState(CandyMachine mCandyMachine) {
        this.mCandyMachine = mCandyMachine;
    }

    @Override
    public void insertCoin() {
        // TODO Auto-generated method stub
        System.out.println("please wait!we are giving you a candy!");

    }

    @Override
    public void returnCoin() {
        // TODO Auto-generated method stub
        System.out.println("you haven't inserted a coin yet!");

    }

    @Override
    public void turnCrank() {
        // TODO Auto-generated method stub
        System.out
                .println("we are giving you a candy,turning another get nothing,!");

    }

    @Override
    public void dispense() {
        // TODO Auto-generated method stub

        
        mCandyMachine.releaseCandy();
        if (mCandyMachine.getCount() == 0) {
            mCandyMachine.setState(mCandyMachine.mSoldOutState);
        } else {
            System.out.println("you are a winner!you get another candy!");
            mCandyMachine.releaseCandy();
            if (mCandyMachine.getCount() > 0) {
                mCandyMachine.setState(mCandyMachine.mOnReadyState);
            } else {
                System.out.println("Oo,out of candies");
                mCandyMachine.setState(mCandyMachine.mSoldOutState);
            }
        }

    }

    @Override
    public void printstate() {
        // TODO Auto-generated method stub
        System.out.println("***WinnerState***");

    }

}

 

3.糖果機,糖果機要持有所有的狀態,並在初始化的時候,要設置其開始的狀態,然后糖果的各個行為,就委托到了各個狀態中自己維護,代碼如下:

package study.designmode.statemode.state;

public class CandyMachine {

    State mSoldOutState;
    State mOnReadyState;
    State mHasCoin;
    State mSoldState;
    State mWinnerState;
    private State state;
    private int count = 0;

    public CandyMachine(int count) {
        this.count = count;
        mSoldOutState = new SoldOutState(this);
        mOnReadyState = new OnReadyState(this);
        mHasCoin = new HasCoin(this);
        mSoldState = new SoldState(this);
        mWinnerState = new WinnerState(this);
        if (count > 0) {
            state = mOnReadyState;
        } else {
            state = mSoldOutState;
        }
    }

    public void setState(State state) {
        this.state = state;
    }

    public void insertCoin() {
        state.insertCoin();
    }

    public void returnCoin() {
        state.returnCoin();
    }

    public void turnCrank() {
        state.turnCrank();
        state.dispense();
    }

    void releaseCandy() {

        // TODO Auto-generated method stub
        if (count > 0) {
            count = count - 1;
            System.out.println("a candy rolling out!");
        }

    }

    public int getCount() {
        return count;
    }

    public void printstate() {
        state.printstate();
    }
}

 

4.測試類

package study.designmode.statemode.state;

public class MainTest {
    public static void main(String[] args) {
        CandyMachine mCandyMachine = new CandyMachine(6);

        mCandyMachine.printstate();

        mCandyMachine.insertCoin();
        mCandyMachine.printstate();

        mCandyMachine.turnCrank();

        mCandyMachine.printstate();

        mCandyMachine.insertCoin();
        mCandyMachine.printstate();

        mCandyMachine.turnCrank();

        mCandyMachine.printstate();
    }
}

 

結果如下:

 

可以和開始的傳統方案對比,結果是一樣的,但是具備了可擴展性。

 

三、總結

通過上面的例子,我們已經對狀態模式有所了解,下面我們做一個總結,來回顧我們的狀態模式:

1.狀態模式允許對象在內部狀態改變時改變它的行為,對象看起來好像修改了它的類。
   理解:這個模式將狀態封裝成獨立的類,並將動作委托到代表當前狀態的對象,這就是說行為會隨着內部狀態而改變。
   “看起來好像修改了它的類”是什么意思呢?從客戶的視角來看:如果說你使用的對象能夠完全改變它的行為,那么你會覺得,這個對象實際上是從別的類實例化而來的。然而,實際上,你知道我們是在使用組合通過簡單引用不同的狀態對象來造成類改變的假象

2.狀態模式要點

(1)客戶不會和狀態進行交互,全盤了解狀態是 context的工作
(2)在狀態模式中,每個狀態通過持有Context的引用,來實現狀態轉移
(3)使用狀態模式總是會增加設計中類的數目,這是為了要獲得程序可擴展性,彈性的代價,如果你的代碼不是一次性的,后期可能會不斷加入不同的狀態,那么狀態模式的設計是絕對值得的。【同時也是一個缺點】
(4)狀態類可以被多個context實例共享

3.狀態模式和策略模式對比

首先讓我們來看看它們之間更多的相似之處:
添加新的狀態或策略都很容易,而且不需要修改使用它們的Context對象。
它們都讓你的代碼符合OCP原則(軟件對擴展應該是開發的,對修改應該是關閉的)。在狀態模式和策略模式中,Context對象對修改是關閉的,添加新的狀態或策略,都不需要修改Context。
正如狀態模式中的Context會有初始狀態一樣,策略模式同樣有默認策略。
狀態模式以不同的狀態封裝不同的行為,而策略模式以不同的策略封裝不同的行為。
它們都依賴子類去實現相關行為

兩個模式的差別在於它們的”意圖“不同:

狀態模式幫助對象管理狀態,我們將一群行為封裝早狀態對象中,context的行為隨時可委托到那些狀態中的一個.隨着時間的流逝,當前狀態在狀態對象集合中游走改變,以反映context內部狀態,因此,context的行為也會跟着改變。當要添加新的狀態時,不需要修改原來代碼添加新的狀態類即可。 而策略模式允許Client選擇不同的行為。通過封裝一組相關算法,為Client提供運行時的靈活性。Client可以在運行時,選擇任一算法,而不改變使用算法的Context。一些流行的策略模式的例子是寫那些使用算法的代碼,例如加密算法、壓縮算法、排序算法。客戶通常主動指定context所要組合的策略對象是哪一個.

一句話:最根本的差異在於策略模式是在求解同一個問題的多種解法,這些不同解法之間毫無關聯;狀態模式則不同,狀態模式要求各個狀態之間有所關聯,以便實現狀態轉移。


免責聲明!

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



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