初识设计模式 chapter 10-状态模式

初识设计模式 chapter 10-状态模式


1 引言


基本常识:策略模式和状态模式是双胞胎,在出生时才分开。你已经知道了,策略模式是围绕可以互换的算法来创建成功业务的。然而,状态走的是更崇高的路,他通过改变对象内部的状态来帮助对象控制自己的行为。

2 正文


2.1 本章业务背景


本章的业务背景是一个糖果机,它有四种状态:没有25分钱、有25分钱、售出糖果、糖果售罄,可以对糖果机做出的操作有三种:投入25分钱、退回25分钱、转动曲柄、发放糖果,前三种是客户对机器做出的操作,最后一种是糖果机内部的操作。

2.2 以操作为对象的设计


现在我们以操作为对象,根据当前的状态命令糖果机作出下一步动作。
下面给出投入25分钱的方法代码:

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

该来的躲不掉,变更请求。
增加需求:当曲柄被转动时,有10%的几率掉下来两颗糖果。

需求变更导致我们上述的设计暴露出一些弊端:
1、这份代码没有遵守开放-关闭的原则。
2、状态转换被埋藏在条件语句中,所以并不明显。
3、我们还没有把会改变的那部分包装起来。
4、未来加入的代码很有可能导致Bug。

2.3 新的设计


我们的计划是这样的:不要维护我们现有的代码,我们重写它以便于将状态对象封装在各自的类中,然后在动作发生时委托给当前状态。

我们在这里遵照我们的设计原则,所以最后应该得到一个容易维护的设计。我们要做的事情是:
1、首先,我们定义一个state接口。在这个接口内,糖果机的每个动作都有一个对象的方法。
2、然后为机器中的每个状态实现状态类。这些类将负责在对应的状态下进行机器的行为。
3、最后,我们要摆脱旧的条件代码,取而代之的方式是,将动作委托到状态类。

你将会看到,我们不仅遵守了设计原则,实际上我们还实现了状态模式。在重新完成代码之后我们再来了解状态模式的正式定义。

State接口

public interface State {
 
	public void insertQuarter();
	public void ejectQuarter();
	public void turnCrank();
	public void dispense();
}

实现我们的状态类

public class NoQuarterState implements State {
    GumballMachine gumballMachine;
 
    /*
     * 1 首先我们要实现State接口
     * 2 我们通过构造器得到糖果机的引用,然后记录在实例变量中
     * 3 以下分别是四种操作对应的处理方法
     */
    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 String toString() {
		return "waiting for quarter";
	}
}

完整的糖果机类

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

		//如果超过0颗糖果,我们就把状态设为NoQuaterState
		this.count = numberGumballs;
 		if (numberGumballs > 0) {
			state = noQuarterState;
		} 
	}
 
	/*
	 * 1 现在这些动作变得容易实现了。我们只是委托到当前状态
	 * 2 请注意,我们不需要再GumballMachine中转杯一个dispense()的动作方法,
	 *   因为这是一个内部动作;用户不可以直接要求机器发放糖果。但我们再状态对象的
	 *   turnCrank()方法中调用dispense()方法的
	 */
	public void insertQuarter() {
		state.insertQuarter();
	}
 
	public void ejectQuarter() {
		state.ejectQuarter();
	}
 
	public void turnCrank() {
		state.turnCrank();
		state.dispense();
	}

	//这个方法 允许其他的对象(像我们的状态对象)将极其的状态转换到不同的状态
	void setState(State state) {
		this.state = state;
	}
 
	//这个极其提供了一个releaseBall()的辅助方法来释放出糖果,并将count实例变量的值减1
	void releaseBall() {
		System.out.println("A gumball comes rolling out the slot...");
		if (count != 0) {
			count = count - 1;
		}
	}
 
	//下面是一系列的get方法
	int getCount() {
		return count;
	}
 
	void refill(int count) {
		this.count = count;
		state = noQuarterState;
	}

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

2.4 定义状态模式


状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
这个描述中的第一部分附有相当多的涵义,是吧?因为这个模式将状态封装成为独立的类,并将动作委托到代表当前状态的对象,我们知道行为会随着内部状态而改变。糖果机提供了一个很好的例子:当糖果机是在不同状态时,你投入25分钱,就会得到不同的行为。
而这个定义的第二部分呢?一个对象“看起来好像修改了它的类”是什么意思呢?从客户的视角来看:如果说你使用的对象能够完全改变它的行为,那么你会觉得,这个对象实际上是从别的类实例化而来的。然后,实际上,你知道我们是在使用组合通过简单引用不同的状态对象来造成类改变的假象。

一般来说,我们把策略模式想成是除了继承之外的一种弹性替代方案。如果你使用继承定义了一个类的行为,你将被这个行为困住,甚至修改它很难。有了策略模式,你可以通过组合不同的对象改变行为。
我们把状态模式想成是不用在context中放置许多条件判断的替代方案。通过将行为包装进状态对象中,你可以通过在context内简单地改变状态对象来改变context的行为。

2.5 解决幸运糖果的问题


首先,我们增加一个状态,并且在转动曲柄是做一个随机数判断,根据结果决定是否额外多发放一个糖果。

我们为什么要增加一个新的状态?为什么不直接在SoldState中发放两颗糖果?
这是一个好问题。这两个状态几乎一样,唯一的差别在于,WinnerState状态会发放两颗糖果。你当然可以将发放两颗糖果的代码放在SoldState中,当然这么做也有缺点,因为你等于时将两个状态用一个状态类来代表。这样做你牺牲了状态类的清晰易懂来减少一些代码冗余。你也应该考虑到在前面的章节所学到的原则:一个类,一个责任。将WinnerState状态的责任放进SoldState中,你等于是让SoldState状态具有两个责任。那么促销方案结束后或者赢家的几率改变以后,你又该怎么办呢?所以,这必须用的只会来做折中。

3 本章总结

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

状态模式,根据字面意思其实很好理解。我们要处理的业务是一个流程,流程是由两部分组成:状态和动作,动作可以根据当前的状态(或者其他上下文)判断从而命令实例改变行为。缺陷管理流程大家应该都很熟悉,测试人员new一个bug report给测试经理,再由测试经理转给开发经理,开发经理根据模块分配给相关的开发人员,开发人员修改完成后转给组内人员进行校验,校验成功后转给开发经理,开发经理转给测试经理,测试经理转给测试人员,回归通过,问题单关闭。上述只列出了正常流程,每个状态都可以因为一些上下文原因将流程回退,比如测试经理发现bug report是个非问题,则将bug report走回,等等,这个流程相比本章的糖果机可能要复杂一点,但确实是一个很好的锻炼机会,有兴趣地朋友可以实现以下噢。







  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值