策略模式和状态模式是双胞胎,在出生时才分开。你已经知道,策略模式是围绕可以互换的算法来创建成功业务的,然而,状态走的是更崇高的路,它通过改变对象内部的状态来帮助对象控制自己的行为。
题例:万能糖果公司
我们认为糖果机的控制器需要如下图般的工作,希望你能用Java语言帮我们实现它,而且需要让设计能够尽量有弹性而且好维护,因为将来我们可能要为它增加更多的行为。
状态机101
没错上图是一个状态图。
1:首先找出所有的状态:“没有25分钱”,“有25分钱”,“糖果售罄”,“售出糖果”。
2:接下来,创建一个实例变量来持有目前的状态,然后定义每个状态的值:
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;
3:现在,我们将所有系统中可以发生的动作整合起来:
“投入25分钱”,“退回25分钱”,“转动曲柄”,“发放糖果”
这些动作是糖果机的接口,这是你能对糖果机做的事情,
调用任何一个动作都会造成状态的转换,
发放糖果更多是糖果机的内部动作,机器自己调用自己。
4:现在,我们创建了一个类,它的作用就像是一个状态机,第每一个动作,我们都创建了一个对应的方法,这些方法利用条件语句来决定在每个状态内什么行为是恰当的。比如对“投入25分钱”这个动作来说,我们可以把对应方法写成下面的样子:
public void insertQuarter(){
if(state==HAS_QUARTER){
}else if(state==SOLD_OUT){
}else if(state ==SOLD){
}else if(state==NO_QUARTER){
}
}
每一个可能的状态都需要用条件语句检查,然后对每一个可能的状态展现适当的行为。
写下代码
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;
}
}
//当有25分钱投入,就会执行这个方法
public void insertQuarter(){
if(state==HAS_QUARTER){
System.out.println("如果已投入过25分钱,我们就告诉顾客");
}else if(state==SOLD_OUT){
state=HAS_QUARTER;
System.out.println("如果是在“没有25分钱”的状态下,我们就接手25分钱," +
"并将状态展缓到“有25分钱”的状态");
}else if(state ==SOLD){
System.out.println("如果糖果已经售罄,我们就拒绝收钱");
}else if(state==NO_QUARTER){
System.out.println("如果顾客刚才买了糖果,就需要稍等一些,好让状态转换完毕。" +
"恢复到“没有25分钱”的状态");
}
}
//先在,如果顾客试着退回25分钱就执行这个方法
public void ejectQuarter(){
if(state==HAS_QUARTER){
System.out.println("如果有25分钱,我们就把钱退出来,回到“没有25分钱”的状态");
state=NO_QUARTER;
}else if(state==SOLD_OUT){
System.out.println("如果没有25分钱的话,当然不能退出25分钱");
}else if(state ==SOLD){
System.out.println("顾客已经转动曲柄就不能再退钱了,他已经拿到糖果了");
}else if(state==NO_QUARTER){
System.out.println("如果糖果售罄,就不能接受25分钱,当然也不可能退钱");
}
}
//顾客试着转动曲柄
public void turnCrank(){
if(state==HAS_QUARTER){
System.out.println("别想骗过机器拿两次糖果");
}else if(state==SOLD_OUT){
System.out.println("我们需要先投入25分钱");
}else if(state ==SOLD){
System.out.println("我们不能给糖果,已经没有任何糖果了");
}else if(state==NO_QUARTER){
System.out.println("成功,他们拿到糖果了," +
"改变状态到“售出糖果”然后条用机器的disoense()方法");
state=SOLD;
dispense();
}
}
//调用此方法,发放糖果
public void dispense(){
if(state==HAS_QUARTER){
System.out.println("我们正在”出售糖果“状态,给他们糖果");
count=count-1;
/*
我们在这里处理“糖果售罄的情况,如果这是最后一个糖果,将机器的状态设置到“糖果售罄””
否则就回到“没有25分钱”的状态
*/
if(count==0){
System.out.println();
state=SOLD_OUT;
}else{
state=NO_QUARTER;
}
}else if(state==SOLD_OUT){
System.out.println("这些都不应该发生,但是如果做了,就得到错误提示");
}else if(state ==SOLD){
System.out.println("这些都不应该发生,但是如果做了,就得到错误提示");
}else if(state==NO_QUARTER){
System.out.println("这些都不应该发生,但是如果做了,就得到错误提示");
}
}
}
该来的躲不掉……变更请求
已经把你的代码放进他们的机器中,到目前为止一切很顺利。现在需要把“购买糖果”编程一个游戏,这样可以大大增加我们的销售量。
当曲柄被转动时,有10%的几率掉下来的是两个糖果
混乱的状态
当你回顾上面的代码,并开始考虑如何修改它的时候
1:首先,你必须加上一个新的状态,称为“赢家”,这还不算太麻烦
2:然后,你必须在每个方法中加入一个新的条件判断来处理“赢家”状态,这可有你忙的。
3:特别是turnCrack()方法,会变的一团乱,因为你必须加上代码来检查目前的顾客是否是赢家,然后决定是切换到赢家状态还是售出糖果状态。
新的设计
不要维护我们现有的代码,我们重写它以便于将状态对象封装在各自的类中,然后在动作发生时委托给当前状态。
1:首先,我们定义一个State接口,在这个接口内,糖果机的每个动作都有一个对应的方法。
2:然后为机器中的每个状态实现状态类,这些类将负责在对应的状态下进行机器的行为。
3:最后,我们要摆脱旧的条件代码,取而代之的方法是,将动作委托到状态类。
现在我们要把一个状态的所有行为放在一个类中,这么一来我们将行为局部化了,并使得事情更容易改变和理解。
定义状态接口和类
我们先完成第一个版本的糖果机的重新实现之后,再回来处理添加“赢家”的修改。
首先让我们创建一个State接口,所有的状态都必须实现这个接口:
interface State{
void insertQuarter();
void ejectQuarter();
void turnCrank();
void dispense();
}
然后将设计中的每个状态都封装成一个类,每个都实现state接口:
实现我们的状态类:
class NoQuarterState implements State{
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine){
this.gumballMachine=gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("如果有人投入了25分钱,我们就打印出一条消息,说我们接受了25分钱," +
"然后改变机器的状态到HasQuarterState");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
@Override
public void ejectQuarter() {
System.out.println("如果没给钱,就不要求退钱");
}
@Override
public void turnCrank() {
System.out.println("如果没给钱,就不能要求糖果");
}
@Override
public void dispense() {
System.out.println("如果没得到钱,我们就不能发放糖果。");
}
}
重新改造糖果机
class GumballMachine_ {
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State state = soldState;
int count = 0;
public GumballMachine_(int count) {
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldOutState = new SoldState(this);
this.count = count;
if (count > 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();
if(count!=0){
count=count-1;
}
}
public State getSoldState() {
return soldState;
}
public State getHasQuarterState() {
return hasQuarterState;
}
public State getNoQuarterState() {
return noQuarterState;
}
public State getSoldOutState() {
return soldOutState;
}
}
实现更多的状态
HasQuarterState(有25分钱)和SoldState(售出糖果)类……
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("退出顾客的25分钱,并将状态转换到NoQuarterState状态");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
@Override
public void turnCrank() {
System.out.println("当曲柄转动,我们将状态转换到SoldState");
gumballMachine.setState(gumballMachine.getSoldState());
}
@Override
public void dispense() {
System.out.println("这是次状态的另一个不恰当动作");
}
}
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{
gumballMachine.setState(gumballMachine.getSoldState());
}
}
}
定义状态模式
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
你会发现策略模式和这张类图根本就是一模一样。
但是这两个模式的差别在于它们的“意图”
以状态模式而言,我们将一群行为封装在状态对象中,context的行为随时可委托到那些状态对象中的一个,随着时间而流逝,当前状态在状态对象集合中游走改变,以反映出context内部的状态,因此,context的行为也会跟着改变,但是context的客户对于状态对象了解不多,甚至根本是浑然不觉。
而策略模式而言,客户通常主动指定Context所要组合的策略对象时哪一个。现在,固然策略模式让我们具有弹性,能够在运行时改变策略,但对于某个context对象来说,通常都只有一个最适当的策略对象。
一般的,我们把策略模式想成是除了继承之外的一种弹性替代方案,如果你使用继承定义了一个类的行为,你将被这个行为困住,是指要修改它都很难,有了策略模式,你可以通过组合不同的对象来改变行为。
我们把状态模式想成是不用咋icontext中防止旭东条件判断的替代方案,通过将行为包装进状态对象中,你可以通过在context内简单地改变状态对象来改变context的行为。
十次抽中一次的游戏,尚未解决……
首先要在GumballMahine类中加入一个状态:
class GumballMachine_ {
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State winnerState;
State state = soldState;
int count = 0;
//其他方法
}
class WinnerState implements State{
GumballMachine_ gumballMachine;
public WinnerState(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.getSoldState());
}else{
gumballMachine.releaseBall();
if(gumballMachine.getCount()>0){
gumballMachine.setState(gumballMachine.getNoQuarterState());
}else{
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
}
完成这个游戏
我们需要实现机会随机数,还要增加一个进入WinnerState状态的转换,这两件事都要加进HasQuarterState,因为顾客会从这个状态中转动曲柄:
class HasQuarterState implements State {
Random random = new Random(System.currentTimeMillis());
GumballMachine_ gumballMachine;
public HasQuarterState(GumballMachine_ gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("这是一个对此状态不恰当的动作");
}
@Override
public void ejectQuarter() {
System.out.println("退出顾客的25分钱,并将状态转换到NoQuarterState状态");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
@Override
public void turnCrank() {
System.out.println("当曲柄转动,我们将状态转换到SoldState");
int winner = random.nextInt(10);
if ((winner == 0) && (gumballMachine.getCount() > 1)) {
gumballMachine.setState(gumballMachine.getWinnerState());
} else {
gumballMachine.setState(gumballMachine.getSoldState());
}
}
@Override
public void dispense() {
System.out.println("这是次状态的另一个不恰当动作");
}
}
共同学习,写下你的评论
评论加载中...
作者其他优质文章