開發之路(設計模式十一:狀態模式)

經過改變對象內部狀態幫助對象控制行爲

以一個簡單例子說明,假設咱們要模擬製造一臺糖果機器,對方給你的機器流程圖以下
圖片描述編程

ok,咱們如今簡單分析這張狀態圖,可將狀態提取出來:有硬幣,無硬幣,售出糖果,糖果售空四個狀態,行爲動做提取出來:投入一個硬幣,退回一個硬幣,轉動曲柄,發放糖果四個行爲,固然還有一些特殊狀況,具體狀況參考代碼設計模式

代碼以下(參考)app

GumballMachinedom

public class GumballMachine {
    /**
     * 狀態模式 糖果機的狀態都用一個不一樣整數表示
     * 
     */
    final static int SOLD_OUT = 0;// 糖果售空
    final static int NO_QUARTER = 1;// 沒有投25分錢
    final static int HAS_QUARTER = 2;// 投了25分錢
    final static int SOLD = 3;// 糖果售出

    // 當前狀態
    int state = SOLD_OUT;
    // 用來追蹤糖果數量
    int count = 0;

    public GumballMachine(int count) {
        this.count = count;
        if (count > 0) {
            // 若是有糖果,機器變爲沒有投25分錢狀態(待購買狀態)
            state = NO_QUARTER;
        }
    }

    // 投入25分錢方法
    public void insertQuarter() {
        if (state == HAS_QUARTER) {
            System.out.println("已經投過幣了,不能再投了");
        } else if (state == NO_QUARTER) {
            state = HAS_QUARTER;
            System.out.println("請投入一枚硬幣");
        } else if (state == SOLD_OUT) {
            System.out.println("不能再投幣了,機器已經售空了");
        } else if (state == SOLD) {
            System.out.println("請稍等,正在爲你出糖果");
        }
    }

    // 退出25分錢方法
    public void ejectQuarter() {
        // 1\當客戶要退錢是
        if (state == HAS_QUARTER) {
            System.out.println("零錢退回");
            state = NO_QUARTER;// 進入沒投幣狀態
        } else if (state == NO_QUARTER) {
            System.out.println("你沒有投入硬幣");
        } else if (state == SOLD) {
            System.out.println("對不起,你已經轉動了曲軸,沒法退幣了");
        } else if (state == SOLD_OUT) {
            System.out.println("糖果售空,沒法退幣");
        }
    }

    // 轉動曲軸方法(顧客)
    public void turnCrank() {
        if (state == HAS_QUARTER) {
            System.out.println("請轉動曲軸");
            state = SOLD;// 狀態變爲售出狀態
            dispense();// 調用發放糖果方法
        } else if (state == SOLD) {
            System.out.println("轉動兩次也不給你糖果");
        } else if (state == NO_QUARTER) {
            System.out.println("請先投硬幣");
        } else if (state == SOLD_OUT) {
            System.out.println("機器售空");
        }

    }

    // 發放糖果方法
    private void dispense() {
        if (state == SOLD) {
            System.out.println("一包糖果出來了");
            count -= 1;
            // 當糖果數量0時候呢?
            if (count == 0) {
                System.out.println("哎呀,糖果售空了~~");
                state = SOLD_OUT;
            } else {
                state = NO_QUARTER;
            }
        } else if (state == NO_QUARTER) {
            System.out.println("你須要投入硬幣");
        } else if (state == SOLD_OUT) {
            System.out.println("沒有糖果了");
        } else if (state == HAS_QUARTER) {
            System.out.println("沒有糖果了");
        }
    }

    // 重寫toString()方法輸入糖果機信息
    @Override
    public String toString() {
        StringBuffer result = new StringBuffer();
        result.append("歡迎使用糖果機\n");
        result.append("糖果數量:" + count + "\n");
        result.append("當前糖果機狀態:" + state);
        return result.toString();
    }
}

TestMainide

public class TestMain {
    public static void main(String[] args) {
        // 裝了5個糖
        GumballMachine gumballMachine = new GumballMachine(5);
        System.out.println(gumballMachine + "\n");// 打印糖果機信息

        gumballMachine.insertQuarter();// 投入硬幣
        gumballMachine.turnCrank();// 轉動曲柄        
        System.out.println(gumballMachine + "\n");// 打印糖果機信息

        gumballMachine.insertQuarter();// 投入硬幣
        gumballMachine.ejectQuarter();// 要求退幣
        gumballMachine.turnCrank();// 轉動曲柄,拿不到糖果
        System.out.println(gumballMachine + "\n");// 打印糖果機信息

        gumballMachine.insertQuarter();// 投入硬幣
        gumballMachine.turnCrank();// 轉動曲柄(拿到糖果)
        gumballMachine.insertQuarter();// 投入硬幣
        gumballMachine.turnCrank();// 轉動曲柄(拿到糖果)
        gumballMachine.ejectQuarter();// 要求機器退錢
        System.out.println(gumballMachine + "\n");// 打印糖果機信息

        gumballMachine.insertQuarter();// 投入硬幣
        gumballMachine.insertQuarter();// 投入硬幣
        gumballMachine.turnCrank();// 轉動曲柄(拿到糖果)
        // 下面開始壓力測試
        //gumballMachine.insertQuarter();// 投入硬幣
        //gumballMachine.turnCrank();// 轉動曲軸
        //gumballMachine.insertQuarter();// 投入硬幣
        //gumballMachine.turnCrank();// 轉動曲軸
        System.out.println(gumballMachine + "\n");// 打印糖果機信息
    }
}

效果圖(無壓力測試)
圖片描述測試

效果圖(壓力測試)
圖片描述this

效果能夠快速實現,但這樣的代碼顯然還有不足之處,假設有個新的需求:增長一個幸運用戶,就是購買者有10%的機率能夠一次買到兩顆糖果,這又要如何實現呢?先看流程圖
圖片描述spa

按照以前的1.0代碼加新中獎者功能,顯然不合適,要在每一個方法裏寫入,顯然太麻煩,這裏就須要重構代碼了。咱們能夠試着行爲封裝起來,也能夠把糖果機器具體一下.咱們要作的是以下:
一、首先定義一個State接口,在這個接口內,糖果機的每一個動做都有一個對應的方法。
二、而後爲機器中的每一個狀態實現類。這些類將負責在對應的狀態下進行機器的行爲。
三、重構舊代碼,取而代之方式是將動做委託給狀態類。設計

具體實現請看代碼
結構圖
圖片描述code

State

package Interface;

public interface State {

    /**
     * 將四種狀態抽象出來成基類
     */

    // 投入硬幣
    public void insertQuarter();

    // 退回硬幣
    public void ejectQuarter();

    // 轉動曲柄
    public void turnCrank();

    // 發放糖果
    public void dispense();
}

GumballMachine

package Machine;

public class GumballMachine {
    /**
     * 不在使用靜態整數,都是用對象
     * 
     */
    State soldOutState;// 糖果售空
    State noQuarterState;// 沒有投幣
    State hasQuarterState;// 投了幣
    State soldState;// 糖果售出
    State winnerState;// 中獎狀態
    // 當前狀態,持有的是(糖果售空)對象
    State state = soldOutState;
    int count = 0;

    // numberGumball構造器取得糖果的初始數目後,並把它存放在一個實例變量中
    public GumballMachine(int numberGumballs) {
        soldOutState = new SoldOutState(this);
        noQuarterState = new NoQuarterState(this);
        hasQuarterState = new HasQuarterState(this);
        soldState = new SoldState(this);
        winnerState = new WinnerState(this);

        this.count = numberGumballs;
        if (numberGumballs > 0) {
            state = noQuarterState;
        }
    }

    /**
     * 機器的各個操做不在這裏具體實現了 而是丟給接口,再讓具體實現類去實現接口
     */
    // 添加硬幣
    public void insertQuarter() {
        state.insertQuarter();
    }

    // 退出硬幣
    public void ejectQuarter() {
        state.ejectQuarter();
    }

    // 使用曲柄
    public void turnCrank() {
        state.turnCrank();
        state.dispense();
    }

    // 變化狀態
    public void setState(State state) {
        this.state = state;
    }

    // 糖果出貨
    public void releaseBall() {
        System.out.println("一包糖果出來了");
        if (count != 0) {
            count = count - 1;
        }
    }

    public int getCount() {
        return count;
    }

    public State getState() {
        return state;
    }

    public State getSoldOutState() {
        return soldOutState;
    }

    public State getNoQuarterState() {
        return noQuarterState;
    }

    public State getHasQuarterState() {
        return hasQuarterState;
    }

    public State getSoldState() {
        return soldState;
    }

    public State getWinnerState() {
        return winnerState;
    }

    public String toString() {
        StringBuffer result = new StringBuffer();
        result.append("歡迎使用糖果機\n");
        result.append("糖果數量:" + count + "\n");
        result.append("當前糖果機狀態: " + state + "\n");
        return result.toString();
    }
}

各個行爲類實現
HasQuarterState

package State_Implements;

public class HasQuarterState implements State {
    /**
     * 投幣的實現類
     * 
     * @param gumballMachine
     */
    // 增長一個隨機數產生器,10%機會
    Random randomWinner = new Random(System.currentTimeMillis());
    GumballMachine gumballMachine;

    public HasQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    public void insertQuarter() {
        System.out.println("你已經投過幣了,不能再投了");
    }

    public void ejectQuarter() {
        System.out.println("硬幣退出");
        // 退出硬幣後,狀態變爲沒有硬幣(待購買)狀態
        gumballMachine.setState(gumballMachine.getNoQuarterState());
    }

    public void turnCrank() {
        System.out.println("轉動.....");
        int winner = randomWinner.nextInt(10);// 產生0-9隨機數,當是0而且還有糖果的時候中獎了
        if ((winner == 0) && (gumballMachine.getCount() > 1)) {
            gumballMachine.setState(gumballMachine.getWinnerState());
        } else {
            gumballMachine.setState(gumballMachine.getSoldState());
        }
    }

    public void dispense() {
        System.out.println("沒有糖果出來");
    }

    public String toString() {
        return "等待使用曲柄";
    }
}

NoQuarterState

package State_Implements;
/**
 * 沒有投幣狀態實現,
 * 都統統要實現狀態基類
 * @author Joy
 *
 */
public class NoQuarterState implements State {
    GumballMachine gumballMachine;
 
    public NoQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
    public void insertQuarter() {
        System.out.println("你投了一個硬幣");
        gumballMachine.setState(gumballMachine.getHasQuarterState());
    }
 
    public void ejectQuarter() {
        System.out.println("你沒有投幣,沒法退幣~~");
    }
 
    public void turnCrank() {
        System.out.println("你沒有投幣,沒法繼續~~");
     }
 
    public void dispense() {
        System.out.println("你須要投幣才能買糖果");
    } 
 
    public String toString() {
        return "正在運營";
    }
}

SoldOutState

package State_Implements;

/**
 * 售空狀態
 * 
 * @author Joy
 * 
 */
public class SoldOutState implements State {
    GumballMachine gumballMachine;
 
    public SoldOutState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
    public void insertQuarter() {
        System.out.println("抱歉,你不能在投幣了,糖果售空了");

    }
 
    public void ejectQuarter() {
        System.out.println("抱歉,糖果售空,沒法退幣");
    }
 
    public void turnCrank() {
        System.out.println("抱歉,轉動曲柄無效,糖果售空了");

    }
 
    public void dispense() {
        System.out.println("糖果售空了");
    }
 
    public String toString() {
        return "糖果售空";
    }
}

SoldState

package State_Implements;
/**
 * 賣出糖果狀態
 * 
 * @author Joy
 * 
 */
public class SoldState implements State {
    GumballMachine gumballMachine;
 
    public SoldState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
       
    public void insertQuarter() {
        System.out.println("請稍等,糖果正出貨");
    }
 
    public void ejectQuarter() {
        System.out.println("抱歉,你已使用曲柄,沒法退幣");
    }
 
    public void turnCrank() {
        System.out.println("曲柄不可重複使用");
    }
 
    public void dispense() {
        // 調用糖果出貨方法
        gumballMachine.releaseBall();
        if (gumballMachine.getCount() > 0) {
            gumballMachine.setState(gumballMachine.getNoQuarterState());
        } else {
            System.out.println("哎呀,糖果售空了");
            gumballMachine.setState(gumballMachine.getSoldOutState());
        }
    }
 
    public String toString() {
        return "一個糖果已出貨";
    }
}

WinnerState

package State_Implements;
public class WinnerState implements State {
    GumballMachine gumballMachine;
 
    public WinnerState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
    public void insertQuarter() {
        System.out.println("不能投幣");
    }
 
    public void ejectQuarter() {
        System.out.println("不能投幣");
    }
 
    public void turnCrank() {
        System.out.println("不能使用曲柄");
    }
 
    public void dispense() {
        System.out.println("恭喜中獎了,你獲得兩個糖果~~");
        gumballMachine.releaseBall();
        // 此時糖果機器裏只有一顆時候,那麼第二顆就出不來,狀態變爲售空狀態
        if (gumballMachine.getCount() == 0) {
            gumballMachine.setState(gumballMachine.getSoldOutState());
        } else {
            gumballMachine.releaseBall();
            if (gumballMachine.getCount() > 0) {
                gumballMachine.setState(gumballMachine.getNoQuarterState());
            } else {
                System.out.println("哎呀,糖果售空了");
                gumballMachine.setState(gumballMachine.getSoldOutState());
            }
        }
    }
 
    public String toString() {
        return "你是中獎者,獲得兩個糖果";
    }
}

GumballMachineTestDrive 測試類

package TestMain;

import Machine.GumballMachine;

public class GumballMachineTestDrive {

    public static void main(String[] args) {
        // 一開始5顆糖
        GumballMachine gumballMachine = new GumballMachine(5);
        System.out.println(gumballMachine + "\n");

        gumballMachine.insertQuarter();
        gumballMachine.turnCrank();
        System.out.println(gumballMachine + "\n");// 輸出狀態

        gumballMachine.insertQuarter();
        gumballMachine.turnCrank();
        gumballMachine.insertQuarter();
        gumballMachine.turnCrank();
        System.out.println(gumballMachine + "\n");// 輸出狀態
    }
}

效果圖
圖片描述
圖片描述
圖片描述

狀態模式定義:容許對象在內部狀態改變時改變它的行爲,對象看起來好像修改了它的類。
注:這個模式是將狀態封裝成爲獨立地類,並將動做委託給表明當前狀態的對象。

狀態模式類圖以下,後來本人在回顧時發現狀態模式和策略模式類圖很類似,有興趣朋友能夠將二者去比較不一樣
圖片描述

感謝你看到這裏,狀態模式到這裏就結束了,本人文筆隨便,如有不足或錯誤之處望給予指點,90度彎腰~~~很快我會發佈下一個設計模式的內容,生命不息,編程不止!

參考書籍:《Head First 設計模式》
相關文章
相關標籤/搜索