10:状态模式

本文介绍了如何使用状态模式来实现一个复杂的糖果机,包括不同状态间的转换,如‘没有25分钱’、‘有25分钱’、‘售出糖果’等。在原有基础上,文章还提出了新的需求,即当用户转动曲柄时,有一定概率获得额外糖果,为此引入了‘中奖’状态。通过对糖果机的内部状态建模,避免了条件判断,提高了代码可读性和可维护性。在实现过程中,详细展示了如何创建状态接口、具体状态类,以及如何在糖果机类中进行状态切换。
摘要由CSDN通过智能技术生成

1.引入

1.1 先前引入

基本常识:策略模式和状态模式是双胞胎,在出生时才分开。

你已经知道了,策略模式是围绕可以互换的算法来创建成功业务的。然而,状态走的是更崇高的路,它通过改变对象内部的状态来帮助对象控制自己的行为。它常常告诉它的对象客户“跟着我念:我很棒,我很聪明,我最优秀了……”

1.2 需求引入

需要制作一个糖果机,使之满足下面的工作状态。

“没有25分钱”状态,指的是 “未投币状态”。

投入25分钱之后,就会进入“有25分钱”状态。

在“有25分钱”状态,就可以扭动曲柄改变到“售出糖果”状态,或者退还硬币回到“没有25分钱”状态。

则一共有四种动作:“投入25分钱”、“退回25分钱”、“转动曲柄”和“发放糖果”。

在发放的时候,要在“售出糖果”的状态中测试,是否糖果数目已经为零,来决定是否要进入到“糖果售罄”状态,或者进入“没有25分钱”状态。因此,有五个状态转换(上图中的5个箭头)。

同时也需要注意一些没意义的事情,比如,当糖果机在“没有25分钱”状态的时候,试着去退回25分钱,或者在糖果机内同时放进2个25分钱。

1.3 使用状态机实现

从状态图得到真正的代码:

1.首先,找出所有的状态

2.创建一个实例变量来持有目前的状态,然后定义每个状态的值

3.现在,将所有系统中可以发生的动作整合起来

 4.创建了一个类,它的作用就像是一个状态机。对每一个动作,我们都创建了一个对应的方法,这些方法利用条件语句来决定在每个状态内什么行为是恰当的。比如对“投入25分钱”这个动作来说,我们可以把对应方法写成下面的样子:

 我们在这里所谈沦的是一个通用的技巧:如何对对象内的状态建模——通过创建一个突例变量来持有状态值,并在方法内书写条件代码来处理不同状态

1.4 实现糖果机

现在我们来实现糖果机。我们知道要利用实例变量持有当前的状态,然后需要处理所有可能发生的动作、行为和状态的转换。我们需要实现的动作包括:投入25分钱、退回25分钱、转动曲柄和发放糖果,也要检查糖果是否售罄。

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;
		//库存不为0,则会进入"没有25分钱状态"。如果为0,则保持在“售罄”状态
		if (count > 0) {
			state = NO_QUARTER;
		}
	}

	//动作:投入25分钱(QUARTER)
	//根据机器现有的状态,分别进行转态转换
	public void insertQuarter() {
		if (state == HAS_QUARTER) {//机器现在状态为 已经有25分钱了  就输出 不能再投了
			System.out.println("You can't insert another quarter");
		} else if (state == NO_QUARTER) {//机器现在状态为 无25分钱  此时投入就进行状态转换->投入25分钱
			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) {//如果机器刚刚买了糖果,则机器状态就是 SLOD,那么状态从SOLD->NO_QUARTER
			//这个逻辑就是,机器还停留在上一笔的订单中,需要等待一会,机器进行状态转换,恢复到“没有25分钱”的状态
        	System.out.println("Please wait, we're already giving you a gumball");
		}
	}

	//动作:退回25分钱
	//根据机器现有的状态,分别进行转态转换
	public void ejectQuarter() {
		if (state == HAS_QUARTER) {//机器现状态为 已经有25分钱  则将钱退回并改变状态
			System.out.println("Quarter returned");
			state = NO_QUARTER;
		} else if (state == NO_QUARTER) {//机器现状态为 无25分钱 就输出 没有投币
			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;
	}

	@Override
	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();
	}
}



1.5 内部测试

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);
	}
}

2  变更请求

将“购买糖果”变成是一个游戏,调整机器的收费机制,当曲柄被转动时,有10%的几率掉下来的是两个糖果。

2.1  处理请求

将每个状态的行为都放在各自的类中,每个状态只要实现它自己的动作就可以了。糖果机只需要委托给代表当前状态的状态对象。

重写计划:不维护1.4中的代码,重写这个代码以便于将状态对象封装在各自的类中,然后在动作发生时委托给当前状态

需要做的事情为:

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

2.2 定义状态接口和类

首先,创建一个State接口,所有的状态都必须实现这个接口:

先创建一个总的状态接口State,然后所有的动作对应的方法全部在这个接口里进行实现。

2.3  明确状态类之间的转换关系

*当前状态**经历有效动作* *变换状态*
  没有25分钱投入25分钱有25分钱
有25分钱退回25分钱没有25分钱
有25分钱转动曲柄售出糖果
售出糖果(内部动作,糖果数量>0)发放糖果没有25分钱
售出糖果(内部动作,糖果数量=0)没有糖果糖果售罄

注:其中 对于糖果数量的检测,是在发放之后,再去检测,因此不存在吞币的情况,即投币了之后发现没糖果了,也不给退币

2.4 实现NoQuarterState状态类

先从NoQuarterState类开始:

//状态类:无币
//表示 糖果机里面没有接收投币,因此这个状态 只能接收投币动作
public class NoQuarterState implements State {
    GumballMachine gumballMachine;

    //构造函数里面传入一个糖果机的引用,将状态与具体的糖果机绑定
    public NoQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
	@Override
	public void insertQuarter() {
		System.out.println("You inserted a quarter");
		//接受投币之后,改变糖果机的机器状态为“已投币”
		gumballMachine.setState(gumballMachine.getHasQuarterState());
	}
 
	//下面几个动作均与“无币”状态无关,即无法操作,因此这些动作只是输出,而不改变糖果机的状态
	@Override
	public void ejectQuarter() {
		System.out.println("You haven't inserted a quarter");
	}
 
	@Override
	public void turnCrank() {
		System.out.println("You turned, but there's no quarter");
	 }
 
	@Override
	public void dispense() {
		System.out.println("You need to pay first");
	} 
	
	@Override
	public void refill() { }
 
	@Override
	public String toString() {
		return "waiting for quarter";
	}
}

关于上面代码中,对“没有25分钱”状态,经历“投币”动作后,需要将糖果机转换为“有25分钱”状态的代码:

@Override
	public void insertQuarter() {
		System.out.println("You inserted a quarter");
		//接受投币之后,改变糖果机的机器状态为“已投币”
		gumballMachine.setState(gumballMachine.getHasQuarterState());
	}

 对这个写法进行分析,就需要对糖果机GumballMachine类的编写思路和代码进行分析。  

2.5 对糖果机GumballMachine类进行分析

分析代码如下:

package designMode.statePattern.gumballState;

public class GumballMachine {
 	//用4个State对象来表示4种状态,所有的状态变量都在构造器中创建并赋值的

	/**各个状态之间的转换关系
	 *
	 *   *当前状态*       	*经历有效动作*	    	 *变换状态*
	 *   没有25分钱      	投入25分钱				 有25分钱
	 *   有25分钱			退回25分钱				 没有25分钱
	 *   有25分钱			转动曲柄				 售出糖果
	 *   售出糖果			(糖果数量>0)发放糖果 	 没有25分钱
	 *   售出糖果			(糖果数量=0)没有糖果		 糖果售罄
	 *
	 *   **注**其中 对于糖果数量的检测,是在发放之后,再去检测,因此不存在吞币的情况,即投币了之后发现没糖果了,也不给退币
	 */
	State soldOutState;	//糖果售罄
	State noQuarterState;//没有25分钱
	State hasQuarterState;//有25分钱
	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;
		}
	}

	/**动作全是 对糖果机的实例进行操作的
	 * 这些 只需要委托到当前状态 即可
	* 因此每个状态类State都要有应对各种动作的处理方法
	 *
	 *
	 * 牢记一点:线程操作对象
	 * 	因此,对对象操作的方法,要全部写在对象自身的类里面
	**/

	//动作:投入25分钱
	public void insertQuarter() {
		state.insertQuarter();
	}

	//动作:退回25分钱
	public void ejectQuarter() {
		state.ejectQuarter();
	}

	//动作:转动曲柄
	public void turnCrank() {
		//不需要在GumballMachine中准备一个dispense()的动作,因为这个是一个内部的动作
		//用户不可以直接要求机器方法糖果
		// 线程(用户)操作对象(糖果机)
		//因为发糖果是有前提条件的,因此该由状态来进行分发
		state.turnCrank();
		state.dispense();//dispense v.分发,分配
	}

	//辅助方法来释放出糖果,并将count实例变量的值减1
	//辅助方法只是用来 供发糖果的类调用的,当调用完之后
	//当调用完之后 表示糖果机里的成员变量count属性 内容发生了改变
	//因此,需要调用糖果机类中 自身的方法来进行修改
	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) {
		//因为每个成员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;
    }
 
	@Override
	public String toString() {
		StringBuilder result = new StringBuilder();
		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();
	}
}

2.6 实现更多的状态

接下来就实现HasQuarterState(有25分钱)

package designMode.statePattern.gumballState;

//import java.util.Random;

//状态类:有币
//表示 糖果机里面接受了一个25分的硬币,在这个状态下,可以接收的动作:退币+转动曲柄
public class HasQuarterState implements State {
	GumballMachine gumballMachine;
 
	public HasQuarterState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}
  
	@Override
	public void insertQuarter() {
		System.out.println("You can't insert another quarter");
	}

	//执行退币,将糖果机的状态修改为 “无币”
	@Override
	public void ejectQuarter() {
		System.out.println("Quarter returned");
		gumballMachine.setState(gumballMachine.getNoQuarterState());
	}

	//动作:扭动曲柄
	@Override
	public void turnCrank() {
		System.out.println("You turned...");
		//根据状态图转换,扭动之后,将糖果机状态修改为 “售出糖果”
		gumballMachine.setState(gumballMachine.getSoldState());
	}

    @Override
	public void dispense() {
        System.out.println("No gumball dispensed");
    }
    
    @Override
	public void refill() { }
 
	@Override
	public String toString() {
		return "waiting for turn of crank";
	}
}

SoldState(售出糖果)类

public class SoldState implements State {
 
    GumballMachine gumballMachine;
 
    public SoldState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
       
	@Override
	public void insertQuarter() {
		System.out.println("Please wait, we're already giving you a gumball");
	}
 
	@Override
	public void ejectQuarter() {
		System.out.println("Sorry, you already turned the crank");
	}
 
	@Override
	public void turnCrank() {
		System.out.println("Turning twice doesn't get you another gumball!");
	}

	//真正的执行动作在这里
	@Override
	public void dispense() {
    	//该状态调用糖果机来发放糖果
		//gumballMachine.releaseBall()用于对糖果机中糖果数量count进行改变--辅助方法
		gumballMachine.releaseBall();
		if (gumballMachine.getCount() > 0) {//判断状态来切换状态,先发再查,肯定不会虚发
			gumballMachine.setState(gumballMachine.getNoQuarterState());
		} else {
			System.out.println("Oops, out of gumballs!");
			gumballMachine.setState(gumballMachine.getSoldOutState());
		}
	}
	
	@Override
	public void refill() { }
 
	@Override
	public String toString() {
		return "dispensing a gumball";
	}
}


2.7 状态改变分析

糖果机持有每个状态类的实例,机器的当前状态总是这些实例之一。

状态转换

 当动作被调用时,它就会被委托到当前的状态。

 从 HasQuarter状态  转变为  Sold状态

 

代码中的状态切换:

 

3.状态模式

3.1 定义状态模式

状态模式定义:

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

从上面的状态改变分析就可以知道,转动曲柄的动作,将糖果机从“有2 5分钱”状态改变到“售卖糖果”状态。

这个模式将状态封装成独立的类,并将动作委托到代表当前状态的对象,我们知道行为会随着内部状态而改变。

定义中的第二部分“对象看起来好像修改了它的类”,从客户角度来看:如果说你是要的对象能够完全改变它的行为,那么你就会觉得,这个对象实际上是从别的类实例化而来的。然而,实际上,你知道我们是在使用组合通过简单引用不同的状态对象来造成类改变的假象

3.2 状态模式的类图

这个类图里面的内容全是重点: 

Context(上下文)是一个类,它可以拥有一些内部状态。在我们的例子中,GumballMachine就是这个Context。

State接口定义了一个所有具体状态的共同接口;任何状态都实现这个相同的接口,这样一来,状态之间可以相互替换。

ConcreteState(具体状态)处理来自Context的请求。每一个ConcreteState都提供了它自己对于请求的实现。所以,当Context改变状态时,行为也跟着改变

3.3 类图与策略模式相同

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

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


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

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

3.4 状态模式问答

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

回答1:不,并非总是如此。Context也可以决定状态转换的流向。一般来讲,当状态转换是固定的时候,就是和放在Context中;然而,当状态转换是更动态的时候,通常就会放在状态类中。例如,在GumballMachine中,由运行时糖果的数目来决定状态要转换到NoQuarter还是SoldOut.

/**SoldState**/

//真正的执行动作在这里
	@Override
	public void dispense() {
    	//该状态调用糖果机来发放糖果
		//gumballMachine.releaseBall()用于对糖果机中糖果数量count进行改变--辅助方法
		gumballMachine.releaseBall();
		if (gumballMachine.getCount() > 0) {//判断状态来切换状态,先发再查,肯定不会虚发
			gumballMachine.setState(gumballMachine.getNoQuarterState());
		} else {
			System.out.println("Oops, out of gumballs!");
			gumballMachine.setState(gumballMachine.getSoldOutState());
		}
	}

在我们的GumballMachine实现汇总,我们试图通过使用Context上的getter方法把依赖减到最小,而不是显示硬编码具体状态类。

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

回答2:不会,状态是永存在Context中来代表他的内部状态以及行为的,所以只有Context才会对状态提出请求。客户不会直接改变Context的状态。全盘了解状态是Context的工作,客户根本不了解,所以不会直接和状态联系。

问题3:如果在程序中Context有许多实例,这些实例之间可以共享状态对象吗?

回答3:是可以的,这也是十分常见的做法。但唯一的前提是,状态对象不能持有他们自己的内部状态;否则就不能共享。 想要共享状态,需要把每个状态都指定到讲台的实例变量中。如果你的状态需要利用到Context中的方法或者实例变量,你还必须在每个handler()方法内传入一个context的引用。

问题4:使用状态模式似乎总是增加我们设计类中的数目。请看GumballMachine的例子,新版本比旧版本多出了许多类。

回答4:没错,在个别的状态类中封装状态行为,结果总是增加这个设计中类的数目。这就是为了要获取弹性而付出的代价。除非你的代码是一次性的,可以用完就扔掉(是呀!才怪! ),那么其实状态模式的设计是绝对值得的。其实真正重要的是你暴露给客户的类数目,而且我们有办法将这些额外的状态类全都隐藏起来。

让我们看一下另一种做法:如果你有一个应用,它有很多状态,但是你决定不将这些状态封装在不同的对象中,那么你就会得到巨大的、整块的条件语句。这会让你的代码不客易维护和理解。通过使用许多对象,你可以让状态变得很干净,在以后理解和维护它们时,就可以省下很多的工夫。

问题5:状态模式类图显示State是一个抽象类,但你不是使用接口实现糖果机状态的吗?

回答5:如果我们没有共同的功能可以放进抽象类中,就会使用接口。在你实现状态模式时,很可能想使用抽象类。这么一来,当你以后需要在抽象类中加入新的方法时就很容易,不需妥打破具体状态的实现。

4.实现抽奖需求

4.1 中奖需求实现

需求在上面已经介绍了,就是需要新增一个中奖状态。因为已经实现了状态模式,实现新状态还是很简单的。

首先,先在GumballMachine中添加WinnerState:

public class GumballMachine {
 
	State soldOutState;
	State noQuarterState;
	State hasQuarterState;
	State soldState;
	State winnerState;//新增加的状态类

    //...一些方法

}

现在再来实现WinnerState类,这个类也实现了State接口:

package designMode.statePattern.gumballStateWinner;

public class WinnerState implements State {
    GumballMachine gumballMachine;
 
    public WinnerState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
	@Override
	public void insertQuarter() {
		System.out.println("Please wait, we're already giving you a Gumball");
	}
 
	@Override
	public void ejectQuarter() {
		System.out.println("Please wait, we're already giving you a Gumball");
	}
 
	@Override
	public void turnCrank() {
		System.out.println("Turning again doesn't get you another gumball!");
	}
 
	@Override
	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());
			}
		}
	}
 
	@Override
	public void refill() { }
	
	@Override
	public String toString() {
		return "despensing two gumballs for your quarter, because YOU'RE A WINNER!";
	}
}

4.2 完善游戏

还需要做一个改变:需要实现机会随机数,并且增加一个进入WinnerState的状态转换。这两件事情都要加进HasQuarterState,因为顾客会从这个状态中转动曲柄:

package designMode.statePattern.gumballStateWinner;

import java.util.Random;

public class HasQuarterState implements State {
	//首先,增加一个随机数产生器,产生10%赢的机会
	Random randomWinner = new Random(System.currentTimeMillis());
	GumballMachine gumballMachine;
 
	public HasQuarterState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}
  
	@Override
	public void insertQuarter() {
		System.out.println("You can't insert another quarter");
	}
 
	@Override
	public void ejectQuarter() {
		System.out.println("Quarter returned");
		gumballMachine.setState(gumballMachine.getNoQuarterState());
	}
 
	@Override
	public void turnCrank() {
		System.out.println("You turned...");
		int winner = randomWinner.nextInt(10);
		//根据随机数,决定这个顾客是否赢了
		if ((winner == 0) && (gumballMachine.getCount() > 1)) {
			//如果赢了,并且有足够的糖果的话,就进入WinnerState状态,否则就进入SlodState
			gumballMachine.setState(gumballMachine.getWinnerState());
		} else {
			gumballMachine.setState(gumballMachine.getSoldState());
		}
	}

    @Override
	public void dispense() {
        System.out.println("No gumball dispensed");
    }
    
    @Override
	public void refill() { }
 
	public String toString() {
		return "waiting for turn of crank";
	}
}

4.3 完整的展示样例

测试代码:

package designMode.statePattern.gumballStateWinner;

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);

	}
}

输出:

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...
You inserted a quarter
You turned...
A gumball comes rolling out the slot...

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 8 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: 5 gumballs
Machine is waiting for quarter

问题:我们为什么需要WinnerState?为什么不直接在SoldState中发放两颗糖果?

回答:这是一个好问题,这两个状态几乎一样,唯一的差别在于,WinnerState状态会发放两颗糖果。你当然可以将发放两颗糖果的代码放在SoldState中,当然这么做有缺点,因为你等于是将两个状态用一个状态类来代表。这样做你牺牲了状态类的清晰易懂来减少一些冗余代码

你也应该考虑到在前面的章节中所学到的原则:一个类,一个责任。将WinnerStatc状态的责任放进SoldState状态中,你等于是让SoldStatc状态具有两个责任。那么促销方案结束之后或者赢家的机率改变之后,你又该怎么办呢?所以,这必须用你的智慧来做折衷。


 

5.总结

  1. 状态模式允许一个对象基于内部状态而拥有不同的行为。
  2. 和程序状态机( PSM)不同,状态模式用类代表状态。
  3. Context会将行为委托给当前状态对象。
  4. 通过将每个状态封装进一个类,我们把以后需要做的任何改变局部化了。
  5. 状态模式和策略模式有相同的类图,但是它们的意图不同。
  6. 策略模式通常会用行为或算法来配置Context类。
  7. 状态模式允许Context随着状态的改变而改变行为。
  8. 状态转换可以由State类或Context类控制。
  9. 使用状态模式通常会导致设计中类的数目大量增加。
  10. 状态类可以被多个Context实例共享。
     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值