狀態模式:允許對象在狀態改變時改變它的行爲,適用於一個任務有多種狀態及多種動作時。
示例演示實現一個自動糖果機,其狀態圖是這個樣子的:
這個狀態圖展現了糖果機可以進行的五個動作和四種狀態,基本思路是將狀態抽象成一個超類或者接口,然後這個超類或接口包含所有這些動作,糖果機擁有不同狀態的實現,這些不同的實現能正確處理在這種狀態下某個動作並將糖果機的狀態切換到變化後的狀態。好吧,說了這麼多,如果不理解就直接看代碼吧。
這裏我們用接口的方式實現,首先是狀態的接口
package com.lttclaw.stateMode;
public interface State {
/**
* 投幣
*/
public void insertQuarter();
/**
* 退幣
*/
public void ejectQuarter();
/**
* 轉動曲柄
*/
public void turnCrank();
/**
* 發放糖果
*/
public void dispense();
}
然後看看糖果機:
package com.lttclaw.stateMode;
public class GumballMachine {
private State noQuarterState;
private State hasQuarterState;
private State soldState;
private State soldOutState;
private State currentState;
private int count=0;
public GumballMachine(int count){
this.count=count;
noQuarterState=new NoQuarterState(this);
hasQuarterState=new HasQuarterState(this);
soldState=new SoldState(this);
soldOutState=new SoldOutState(this);
if(count>0){
setState(noQuarterState);
}else{
setState(soldOutState);
}
}
public void insertQuarter(){
currentState.insertQuarter();
}
public void ejectQuarter(){
currentState.ejectQuarter();
}
public void turnCranck(){
currentState.turnCrank();
currentState.dispense();
}
/**
* 不開放的方法,對包外的客戶不提供此方法
*/
void releaseBall(){
System.out.println("發糖糖啦");
if(count>0){
count--;
}
}
int getCount(){
return count;
}
void setState(State state){
currentState=state;
}
State getNoQuarterState() {
return noQuarterState;
}
State getHasQuarterState() {
return hasQuarterState;
}
State getSoldState() {
return soldState;
}
State getSoldOutState() {
return soldOutState;
}
State getCurrentState() {
return currentState;
}
}
然後我們看看四種狀態是如果實現各自的動作的,先是未投幣狀態,即有庫存的初始狀態:
package com.lttclaw.stateMode;
public class NoQuarterState implements State {
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine=gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("你剛剛投了幣");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
@Override
public void ejectQuarter() {
System.out.println("呃,沒投幣啊,我怎麼退給你??");
}
@Override
public void turnCrank() {
System.out.println("不掏錢是沒有糖滴");
}
@Override
public void dispense() {
}
}
然後是投幣後狀態,即投幣後還未拉桿取糖:
package com.lttclaw.stateMode;
public class HasQuarterState implements State {
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine=gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("已經有幣了,先用掉再投吧");
}
@Override
public void ejectQuarter() {
System.out.println("退幣了,接着,biubiu");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
@Override
public void turnCrank() {
System.out.println("好,下面將爲你發糖糖");
gumballMachine.setState(gumballMachine.getSoldState());
}
@Override
public void dispense() {
}
}
然後看看賣出狀態,這個狀態不是持續狀態,只會在用戶取糖後短暫進入,然後很快就會根據庫存進入下個狀態:
package com.lttclaw.stateMode;
/**
* @author claw
* 售出糖果狀態
*
*/
public class SoldState implements State {
GumballMachine gumballMachine;
public SoldState(GumballMachine gumballMachine) {
this.gumballMachine=gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("馬上就給糖啦,請稍候~");
}
@Override
public void ejectQuarter() {
System.out.println("馬上就給糖啦,請稍候~");
}
@Override
public void turnCrank() {
System.out.println("馬上就給糖啦,請稍候~");
}
@Override
public void dispense() {
gumballMachine.releaseBall();
if(gumballMachine.getCount()>0){
gumballMachine.setState(gumballMachine.getNoQuarterState());
}else{
System.out.println("最後一顆也賣掉了,現在沒啦");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
再看售罄狀態:
package com.lttclaw.stateMode;
public class SoldOutState implements State {
GumballMachine gumballMachine;
public SoldOutState(GumballMachine gumballMachine) {
this.gumballMachine=gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("抱歉,已經賣完了,自動退幣給您");
}
@Override
public void ejectQuarter() {
System.out.println("這個,沒幣可退了");
}
@Override
public void turnCrank() {
System.out.println("賣完了,搖壞也沒有了");
}
@Override
public void dispense() {
}
}
好了,下面就可以來試試我們的糖果機了,這個測試類我寫在另一個包裏,否則客戶就可以隨意調用我們的內部業務方法了……
package com.lttclaw.client;
import com.lttclaw.stateMode.GumballMachine;
public class Client {
public static void main(String[] args) {
GumballMachine gumballMachine=new GumballMachine(4);
gumballMachine.insertQuarter();
gumballMachine.turnCranck();
gumballMachine.insertQuarter();
gumballMachine.insertQuarter();
gumballMachine.ejectQuarter();
gumballMachine.ejectQuarter();
gumballMachine.turnCranck();
for(int i=0;i<5;i++){
gumballMachine.insertQuarter();
gumballMachine.turnCranck();
}
}
}
運行結果如下:
你剛剛投了幣
好,下面將爲你發糖糖
發糖糖啦
你剛剛投了幣
已經有幣了,先用掉再投吧
退幣了,接着,biubiu
呃,沒投幣啊,我怎麼退給你??
不掏錢是沒有糖滴
不掏錢是沒有糖滴
你剛剛投了幣
好,下面將爲你發糖糖
發糖糖啦
你剛剛投了幣
好,下面將爲你發糖糖
發糖糖啦
你剛剛投了幣
好,下面將爲你發糖糖
發糖糖啦
最後一顆也賣掉了,現在沒啦
抱歉,已經賣完了,自動退幣給您
賣完了,搖壞也沒有了
抱歉,已經賣完了,自動退幣給您
賣完了,搖壞也沒有了
下面總結一下,狀態模式允許對象在內部狀態改變時改變它的行爲,使之看起來好像變成了另一個類。用這種方法實現有較好的可擴展性,如果要加動作,則爲接口添加方法併爲每個實現類實現此方法即可,如果要加狀態,則添加一個實現接口的類即可。其類圖大概長這樣: