《HeadFirst设计模式》第十章-状态模式

1.声明

设计模式中的设计思想、图片和部分代码参考自《Head First设计模式》作者Eric Freeman & Elisabeth Freeman & Kathy Siezza & Bert Bates。

在这里我只是对这本书进行学习阅读,并向大家分享一些心得体会。

2.需求

有一家糖果机公司想让我们帮他们做一套软件系统供糖果机使用,并且他们想要达成如下效果。

《HeadFirst设计模式》第十章-状态模式_第1张图片

这是一张状态图,每个圆圈表示糖果机的一种状态,每个箭头表示状态的转换过程。例如对于糖果机来说,我们投入25分钱,那么糖果机就由"没有25分钱" ——> "有25分钱"这个状态。

如何由状态图得到真正的代码

第一步:

列出糖果机所有可能的状态。经过分析,可以得到如下四种状态:

没有25分钱
有25分钱
糖果售空
售出糖果

第二步:

为每个状态都赋予一个常量值,并且再定义一个变量,表示当前所处的状态:

//售空
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;

第三步:

列出糖果所可能接收到一切动作(指令):

投入25分钱
退回25分钱
转动曲柄
发放糖果

第四步:

创建一个类,这个类的作用就像是一个状态机。对每一个动作( 投入25分钱 ),都需要创建一个对应的方法( insertQuarter() )。

但是糖果机的状态不同,即使接收到相同的动作,那么结果也是不同的。所以在每个方法中,都需要用if分支做判断,根据当前所处的状态,来判断接收到此指令后下个状态是什么。

//投入硬币
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");
	}
}

3.最初的代码设计

糖果机:

//糖果机
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();
	}
}

测试方法:

public class GumballMachineTestDrive {

	public static void main(String[] args) {
		GumballMachine gumballMachine = new GumballMachine(5);

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.ejectQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.ejectQuarter();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);
	}
}

输出结果:

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 5 gumballs
Machine is waiting for quarter

You inserted a quarter
You turned...
A gumball comes rolling out the slot

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 4 gumballs
Machine is waiting for quarter

You inserted a quarter
Quarter returned
You turned but there's no quarter

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 4 gumballs
Machine is waiting for quarter

You inserted a quarter
You turned...
A gumball comes rolling out the slot
You inserted a quarter
You turned...
A gumball comes rolling out the slot
You haven't inserted a quarter

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 2 gumballs
Machine is waiting for quarter

You inserted a quarter
You can't insert another quarter
You turned...
A gumball comes rolling out the slot
You inserted a quarter
You turned...
A gumball comes rolling out the slot
Oops, out of gumballs!
You can't insert a quarter, the machine is sold out
You turned, but there are no gumballs

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 0 gumballs
Machine is sold out

4.万恶的需求变更

糖果公司果然进行了需求变更(基本操作、意料之中),由于糖果公司使用了我们的系统之后,程序果然正常运转了,糖果公司顿时不爽,怒变需求。

修改的需求是:转动曲柄获取糖果时,有10%的几率掉出两个糖果。

《HeadFirst设计模式》第十章-状态模式_第2张图片

虽然我们设计的系统周详、稳定,但是这并不意味着我们的系统就容易扩展。

如果在现有系统上实现,那么就得变成这样:

《HeadFirst设计模式》第十章-状态模式_第3张图片

变更分析:

很遗憾,我们必须得重构代码了,坚持用现有代码,会增加程序bug的几率。

我们应该使者局部化每个状态的行为,这样一来,如果我们针对某个状态做了改变,就不会影响其他代码。我们需要遵守"封装变化"的原则。如果我们将每个变化的行为都放在各自的类中,那么每个状态只要实现它自己的动作就可以了。

这样增加新状态时,虽然我们仍然需要修改代码,但是我们将修改局限在一个很小的范围内,加入一个新状态,我们只需加入一个类还有可能改变一些转换即可。

5.新的设计

  1. 首先定义一个State接口。在这个接口内,糖果机的每个动作都有一个对应的方法。
  2. 然后为机器中的每个状态实现状态类。这些类将负责在对应的状态下进行机器行为。
  3. 最后,摆脱旧的条件代码,取而代之的方式是,将动作委托到状态类。

定义状态接口和类

《HeadFirst设计模式》第十章-状态模式_第4张图片

定义状态接口:

public interface State {
 
	//投入硬币
	public void insertQuarter();
	//退出硬币
	public void ejectQuarter();
	//转动曲柄
	public void turnCrank();
	//发放糖果
	public void dispense();
	
	//充值
	public void refill();
}

定义状态类-无硬币状态:

public class NoQuarterState implements State {
	
	//糖果机
    GumballMachine gumballMachine;
 
    public NoQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
	public void insertQuarter() {
		System.out.println("You inserted a quarter");
		//在"没有硬币"的状态下,投入硬币,糖果机设置新状态为"有硬币"
		gumballMachine.setState(gumballMachine.getHasQuarterState());
	}
 
	public void ejectQuarter() {
		System.out.println("You haven't inserted a quarter");
	}
 
	public void turnCrank() {
		System.out.println("You turned, but there's no quarter");
	 }
 
	public void dispense() {
		System.out.println("You need to pay first");
	} 
	
	public void refill() { }
 
	public String toString() {
		return "waiting for quarter";
	}
}

定义状态类-有硬币状态:

public class HasQuarterState implements State {
	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...");
		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";
	}
}

定义状态类-售空状态:

public class SoldOutState implements State {
    GumballMachine gumballMachine;
 
    public SoldOutState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
	public void insertQuarter() {
		System.out.println("You can't insert a quarter, the machine is sold out");
	}
 
	public void ejectQuarter() {
		System.out.println("You can't eject, you haven't inserted a quarter yet");
	}
 
	public void turnCrank() {
		System.out.println("You turned, but there are no gumballs");
	}
 
	public void dispense() {
		System.out.println("No gumball dispensed");
	}
	
	public void refill() { 
		gumballMachine.setState(gumballMachine.getNoQuarterState());
	}
 
	public String toString() {
		return "sold out";
	}
}

定义状态类-售出状态:

public class SoldState implements State {
 
    GumballMachine gumballMachine;
 
    public SoldState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
       
	public void insertQuarter() {
		System.out.println("Please wait, we're already giving you a gumball");
	}
 
	public void ejectQuarter() {
		System.out.println("Sorry, you already turned the crank");
	}
 
	public void turnCrank() {
		System.out.println("Turning twice doesn't get you another gumball!");
	}
 
	public void dispense() {
		gumballMachine.releaseBall();
		if (gumballMachine.getCount() > 0) {
			gumballMachine.setState(gumballMachine.getNoQuarterState());
		} else {
			System.out.println("Oops, out of gumballs!");
			gumballMachine.setState(gumballMachine.getSoldOutState());
		}
	}
	
	public void refill() { }
 
	public String toString() {
		return "dispensing a gumball";
	}
}

定义糖果机:

糖果机的状态已不再由常量表示,而是由各种State对象表示,这些State对象也是糖果机动作的委托者。

《HeadFirst设计模式》第十章-状态模式_第5张图片

代码:

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;
		}
	}
 
	/*
	 * insertQuarter、
	 * ejectQuarter、
	 * turnCrank
	 * 都委托给当前对象state来处理
	 */
	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; it's 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();
	}
}

测试程序:

public class GumballMachineTestDrive {

	public static void main(String[] args) {
		GumballMachine gumballMachine = new GumballMachine(5);

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.ejectQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.ejectQuarter();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);
	}
}

输出结果:

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 5 gumballs
Machine is waiting for quarter

You inserted a quarter
You turned...
A gumball comes rolling out the slot

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 4 gumballs
Machine is waiting for quarter

You inserted a quarter
Quarter returned
You turned but there's no quarter

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 4 gumballs
Machine is waiting for quarter

You inserted a quarter
You turned...
A gumball comes rolling out the slot
You inserted a quarter
You turned...
A gumball comes rolling out the slot
You haven't inserted a quarter

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 2 gumballs
Machine is waiting for quarter

You inserted a quarter
You can't insert another quarter
You turned...
A gumball comes rolling out the slot
You inserted a quarter
You turned...
A gumball comes rolling out the slot
Oops, out of gumballs!
You can't insert a quarter, the machine is sold out
You turned, but there are no gumballs

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 0 gumballs
Machine is sold out

较最初版,我们做了哪些更改:

  • 将每个状态的行为局部化到它自己的类中。
  • 将容易产生问题的if语句删掉,以方便日后维护。
  • 让每个状态”对修改关闭“,让糖果机”对扩展开放",因为可以加入新的状态类。
  • 创建一个新的代码继承体系,State接口及其实现类,更易理解。

图示 

《HeadFirst设计模式》第十章-状态模式_第6张图片

在"有硬币"的状态下转动曲柄:

《HeadFirst设计模式》第十章-状态模式_第7张图片

将由"有硬币"状态转换到"售出"状态:

《HeadFirst设计模式》第十章-状态模式_第8张图片

6.定义状态模式

状态模式

允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

这个描述的前半句:因为这个模式将状态封装成为独立的类,并将动作委托到代表当前状态的对象,我们知道行为会随着内部状态而改变。糖果机提供了一个很好的例子:当糖果机实在"无硬币"或者"有硬币"的两种状态下,这时投入硬币,会得到不同的行为(机器接收硬币或机器拒绝硬币)。

这个描述的后半句:从客户的视角看:如果说你使用的对象能够完全改变它的行为,那么你会觉得,这个对象实际上是从别的类实例化而来的。然而,实际上,我们知道,我们是在使用组合通过简单引用不同的状态对象造成类改变的假象。

类图

《HeadFirst设计模式》第十章-状态模式_第9张图片

意图

观察这个类图,我们发现和"策略模式"很是相像,但是差别在于它们的"意图"不同。

状态模式:

对于状态模式而言,我们将一群行为封装在状态对象在,context(糖果机)的行为随时可委托到那些状态对象中的额一个。随着时间的流逝,当前状态在状态对象集合中游走改变,以反映出context内部的状态,因此context的行为也会跟着改变,但是context的客户对于状态对象了解不多,甚至浑然不觉。

我们把状态模式想成是不用在context中放置许多条件判断的替代方案。通过将行为包装进状态对象中,我们可以通过在context内简单的改变状态对象来改变context的行为。

策略模式:

对于策略模式而言,客户通常主动指定Context所要组合的策略对象是哪一个。现在,固然策略模式让我们具有弹性,能够在运行时改变策略,但对于某个context对象来说,通常都只要一个最适当的策略对象。比方说,在第一章的策略模式中,有些鸭子(绿头鸭)被设置成利用典型的飞翔行为进行飞翔,而有些鸭子(例如橡皮鸭)使用的飞翔行为只能让他们贴地飞行。

一般来说我们把策略模式想成除了继承之外的一种弹性替代方案。如果我们使用继承定义了一个类的行为,我们将被这个行为困住,甚至要修改它都很难。有了策略模式,我们通过组合不同的对象来改变行为。

7.问答

1)问:在GumballMachine中,状态决定了下一个状态应该是什么。ConcreteState总是决定接下来状态是什么吗?

答:并不总是如此,context也可以决定状态转换的流向。一般来说,当状态转换是固定的时候,就适合放在context中;然而当状态转换是更动态的时候,通常就会放在状态类在(这就是本文的实现方法,例如,在GumballMachine中,由运行时糖果的数目来决定状态要转换到"无硬币"还是"售空")。将状态转换放在状态类中的缺点是:状态类之间产生了依赖。在我们的GumballMachine实现中,我们试图通过使用Context上的getter方法将依赖见到最小,而不是显示的硬编码具体的状态类。状态转换放到哪一个类中,还会影响另一方面,就是我们究竟要对哪个类做"修改关闭",是Context还是状态类)。

2)问:客户会直接和状态交互吗?

答:不会。状态是用在Context中代表它的内部状态以及行为的,所以只有Context才会对状态提出请求。客户不会直接改变Context的状态。全盘了解状态时Context的工作。

3)问:如果在我们的程序中Context由许多实例,这些实例之间可以共享对象吗?

答:绝对可以,事实上这是很常见的做法。但唯一的前提是,我们的状态对象不能持有它们自己的内部状态;否则就不能共享。

想要共享状态,我们需要把每个状态都指定到静态的实例变量中。如果我们的状态需要利用Context中的方法或者实例变量,我们能还需再每个handler()方法内传入一个context的引用。

4)问:使用状态模式似乎总是增加我们设计中类的数目。GumballMachine中,新版比旧版多了很多类。

答:是的,在个别的状态类中封装状态行为,结果总是增加这个设计中类的数目。这就是为了要获取弹性而付出的代价。除非我们的代码时一次性的。可以用完就扔,但是这不可能,那么其实状态模式的设计是绝对值得的。其实真正重要的是我们暴露给客户的类的数目,而且我们有办法将这些额外的状态全部都隐藏起来。举例:如果我们有一个应用,它有很多状态,但是我们决定不将这些状态封装在不同的对象中,那么我们就必须设计巨大的、整块的条件语句。这会让我们的代码不易维护和理解。通过使用许多对象,我们可以让状态变得干净。

8.需求变更具体实现

之前糖果公司要求有10%掉出两个糖果,现在来实现这个需求。

GumballMachine:

public class GumballMachine {
 
	State soldOutState;
	State noQuarterState;
	State hasQuarterState;
	State soldState;
	//新增人生赢家这个状态(其实就是掉出两个糖果)
	State winnerState;
 
	State state = soldOutState;
	int count = 0;
 
	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();
	}

	void setState(State state) {
		this.state = state;
	}
 
	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; it's new count is: " + this.count);
		state.refill();
	}

    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("\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();
	}
}

定义状态类-人生赢家状态:

public class WinnerState implements State {
    GumballMachine gumballMachine;
 
    public WinnerState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
	public void insertQuarter() {
		System.out.println("Please wait, we're already giving you a Gumball");
	}
 
	public void ejectQuarter() {
		System.out.println("Please wait, we're already giving you a Gumball");
	}
 
	public void turnCrank() {
		System.out.println("Turning again doesn't get you another gumball!");
	}
 
	public void dispense() {
		gumballMachine.releaseBall();
		if (gumballMachine.getCount() == 0) {
			gumballMachine.setState(gumballMachine.getSoldOutState());
		} else {
			gumballMachine.releaseBall();
			System.out.println("YOU'RE A WINNER! You got two gumballs for your quarter");
			if (gumballMachine.getCount() > 0) {
				gumballMachine.setState(gumballMachine.getNoQuarterState());
			} else {
            	System.out.println("Oops, out of gumballs!");
				gumballMachine.setState(gumballMachine.getSoldOutState());
			}
		}
	}
 
	public void refill() { }
	
	public String toString() {
		return "despensing two gumballs for your quarter, because YOU'RE A WINNER!";
	}
}

修改状态类-有硬币状态:

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...");
		/*
		 * 顾客有10%几率获胜
		 * 获胜:进入认识赢家状态
		 * 未获胜:进入售出状态
		 */
		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";
	}
}

测试代码:

public class GumballMachineTestDrive {

	public static void main(String[] args) {
		GumballMachine gumballMachine = 
			new GumballMachine(10);

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);
	}
}

输出结果:

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 10 gumballs
Machine is waiting for quarter

You inserted a quarter
You turned...
A gumball comes rolling out the slot...
A gumball comes rolling out the slot...
YOU'RE A WINNER! You got two gumballs for your quarter
You inserted a quarter
You turned...
A gumball comes rolling out the slot...

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 7 gumballs
Machine is waiting for quarter

You inserted a quarter
You turned...
A gumball comes rolling out the slot...
You inserted a quarter
You turned...
A gumball comes rolling out the slot...
A gumball comes rolling out the slot...
YOU'RE A WINNER! You got two gumballs for your quarter

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 4 gumballs
Machine is waiting for quarter

You inserted a quarter
You turned...
A gumball comes rolling out the slot...
You inserted a quarter
You turned...
A gumball comes rolling out the slot...

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 2 gumballs
Machine is waiting for quarter

You inserted a quarter
You turned...
A gumball comes rolling out the slot...
You inserted a quarter
You turned...
A gumball comes rolling out the slot...
Oops, out of gumballs!

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 0 gumballs
Machine is sold out

You can't insert a quarter, the machine is sold out
You turned, but there are no gumballs
No gumball dispensed
You can't insert a quarter, the machine is sold out
You turned, but there are no gumballs
No gumball dispensed

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 0 gumballs
Machine is sold out

9.需要改进的部分

在我们的SoldOutState和WinnerState这两个状态之间,有许多重复的代码,我们可以考虑把State接口设计为抽象类,然后将方法的默认行为放在其中。

 

 

你可能感兴趣的:(设计模式)