状态模式通过改变对象内部的状态来帮助对象控制自己的行为。
这是一张状态图,其中每个圆圈都是一个状态。
最简单,第一反应的实现就是使用一个变量来控制状态值,并在方法内书写条件代码来处理不同情况。
- package headfirst.designpatterns.state.gumball;
-
- public class GumballMachine {
-
- final static int SOLD_OUT = 0;
- final static int NO_QUARTER = 1;
- final static int HAS_QUARTER = 2;
- final static int SOLD = 3;
-
- int state = SOLD_OUT;
- int count = 0;
-
- public GumballMachine(int count) {
- this.count = count;
- if (count > 0) {
- state = NO_QUARTER;
- }
- }
-
- public void insertQuarter() {
- if (state == HAS_QUARTER) {
- System.out.println("You can't insert another quarter");
- } else if (state == NO_QUARTER) {
- state = HAS_QUARTER;
- System.out.println("You inserted a quarter");
- } else if (state == SOLD_OUT) {
- System.out.println("You can't insert a quarter, the machine is sold out");
- } else if (state == SOLD) {
- System.out.println("Please wait, we're already giving you a gumball");
- }
- }
-
- public void ejectQuarter() {
- if (state == HAS_QUARTER) {
- System.out.println("Quarter returned");
- state = NO_QUARTER;
- } else if (state == NO_QUARTER) {
- System.out.println("You haven't inserted a quarter");
- } else if (state == SOLD) {
- System.out.println("Sorry, you already turned the crank");
- } else if (state == SOLD_OUT) {
- System.out.println("You can't eject, you haven't inserted a quarter yet");
- }
- }
-
- public void turnCrank() {
- if (state == SOLD) {
- System.out.println("Turning twice doesn't get you another gumball!");
- } else if (state == NO_QUARTER) {
- System.out.println("You turned but there's no quarter");
- } else if (state == SOLD_OUT) {
- System.out.println("You turned, but there are no gumballs");
- } else if (state == HAS_QUARTER) {
- System.out.println("You turned...");
- state = SOLD;
- dispense();
- }
- }
-
- private void dispense() {
- if (state == SOLD) {
- System.out.println("A gumball comes rolling out the slot");
- count = count - 1;
- if (count == 0) {
- System.out.println("Oops, out of gumballs!");
- state = SOLD_OUT;
- } else {
- state = NO_QUARTER;
- }
- } else if (state == NO_QUARTER) {
- System.out.println("You need to pay first");
- } else if (state == SOLD_OUT) {
- System.out.println("No gumball dispensed");
- } else if (state == HAS_QUARTER) {
- System.out.println("No gumball dispensed");
- }
- }
-
- public void refill(int numGumBalls) {
- this.count = numGumBalls;
- state = NO_QUARTER;
- }
-
- public String toString() {
- StringBuffer result = new StringBuffer();
- result.append("\nMighty Gumball, Inc.");
- result.append("\nJava-enabled Standing Gumball Model #2004\n");
- result.append("Inventory: " + count + " gumball");
- if (count != 1) {
- result.append("s");
- }
- result.append("\nMachine is ");
- if (state == SOLD_OUT) {
- result.append("sold out");
- } else if (state == NO_QUARTER) {
- result.append("waiting for quarter");
- } else if (state == HAS_QUARTER) {
- result.append("waiting for turn of crank");
- } else if (state == SOLD) {
- result.append("delivering a gumball");
- }
- result.append("\n");
- return result.toString();
- }
- }
以上的代码最大的问题就是没有遵守开发-关闭原则,一遇到新的需求(投币后有10%的概率出现“赢家”状态,给出2颗糖果)就需要修改源代码,重新整理所有代码的逻辑。
重构后的代码理念:
- // 每种状态的各个方法的行为都不一样
-
- NoQuarterState
- {
- insertQuarter() // 转到HasQuarterState
- ejectQuarter() // 未投入25分钱
- turnCrank() // 未投入25分钱,转动曲柄无效
- dispense() // 未投入25分钱,不能分发糖果
- }
在新的糖果机中,我们不使用静态整数,而使用state对象。
- public class GumballMachine {
-
- // 所有的状态对象都在构造器中创建并赋值
- State soldOutState;
- State noQuarterState;
- State hasQuarterState;
- State soldState;
-
- State state;
- int count = 0;
-
- public GumballMachine(int numberGumballs) {
- soldOutState = new SoldOutState(this);
- noQuarterState = new NoQuarterState(this);
- hasQuarterState = new HasQuarterState(this);
- soldState = new SoldState(this);
-
- this.count = numberGumballs;
- if (numberGumballs > 0) {
- state = noQuarterState;
- } else {
- state = soldOutState;
- }
- }
-
- public void insertQuarter() {
- state.insertQuarter();
- }
-
- public void ejectQuarter() {
- state.ejectQuarter();
- }
-
- public void turnCrank() {
- state.turnCrank();
- state.dispense();
- }
-
- void releaseBall() {
- System.out.println("A gumball comes rolling out the slot...");
- if (count > 0) {
- count = count - 1;
- }
- }
-
- int getCount() {
- return count;
- }
-
- void refill(int count) {
- this.count += count;
- System.out.println("The gumball machine was just refilled; its new count is: " + this.count);
- state.refill();
- }
-
- void setState(State state) {
- this.state = state;
- }
- 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 String toString() {
- StringBuffer result = new StringBuffer();
- result.append("\nMighty Gumball, Inc.");
- result.append("\nJava-enabled Standing Gumball Model #2004");
- result.append("\nInventory: " + count + " gumball");
- if (count != 1) {
- result.append("s");
- }
- result.append("\n");
- result.append("Machine is " + state + "\n");
- return result.toString();
- }
- }
现在我们已经可以:
状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
策略模式和状态模式的类图是一样的(回去翻了下书,好像没瞅到),但
我们把策略模式想成是除了继承之外的一种弹性替代方案。如果使用继承定义一个类的行为,则会被这个行为困住,很难修改。
状态模式是不用在context中放置许多条件判断的替代方案。通过将行为包装进状态对象中,可以通过在context内简单改变状态对象来改变context的行为。
在GumballMachine中,状态决定了下一个状态应该是什么。ConcreteState总是决定接下来的状态是什么吗?
状态转换是固定的时候,就适合放在Context中。转换是更动态的时候,通常就会放在状态类中。
- // GumballMachine的修改和WinnerState的实现是很简单的
- // 这里就只将HasQuarterState列出
-
- import java.util.Random;
-
- public class HasQuarterState implements State {
- Random randomWinner = new Random(System.currentTimeMillis());
- GumballMachine gumballMachine;
-
- public HasQuarterState(GumballMachine gumballMachine) {
- this.gumballMachine = gumballMachine;
- }
-
- public void insertQuarter() {
- System.out.println("You can't insert another quarter");
- }
-
- public void ejectQuarter() {
- System.out.println("Quarter returned");
- gumballMachine.setState(gumballMachine.getNoQuarterState());
- }
-
- public void turnCrank() {
- System.out.println("You turned...");
- int winner = randomWinner.nextInt(10);
- if ((winner == 0) && (gumballMachine.getCount() > 1)) {
- gumballMachine.setState(gumballMachine.getWinnerState());
- } else {
- gumballMachine.setState(gumballMachine.getSoldState());
- }
- }
-
- public void dispense() {
- System.out.println("No gumball dispensed");
- }
-
- public void refill() { }
-
- public String toString() {
- return "waiting for turn of crank";
- }
- }
------------------