如果要实现一个状态机,对象内的状态建模的通用技巧是 创建一个实例变量来持有状态值并在方法内书写条件代码来处理不同的状态。
设计一大概模版如下
public class TestState{
final static int State1 = 0;
final static int State2 = 1;
public void dosomething{
if(state = State1) ...
if(state = State2)...
}
public class TestState{
final static int State1 = 0;
final static int State2 = 1;
public void dosomething{
if(state = State1) ...
if(state = State2)...
}
这种方式在变更 请求要增加若干个状态时就会陷入混乱的状态,这样所有的表示动作的方法都要进行修改。都能它并没有遵循“封装变化”的原则. 遵循这个原则,我们可以尝试局部化每个状态的行为,这样一来,如果我们针对某个状态做了改变,就不会影响其它的代码。
首先,我们可以定义一个state接口,在这个接口内,状态图的每个动作都有一个对应的方法,。然后为每个状态实现状态类,这些类将负责在对应状态节点进行相应的行为这样我们可以拜托旧的条件代码,将动作委托到状态类。这就是状态模式的基本思想,通过将每个状态封装进一个类,我们把以后需要做的任何改变局部化了
状态模式的定义
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
这个模式将状态封装成独立的类,并将动作委托到代表当前状态的对象,这就是说行为会随着内部状态而改变。
“看起来好像修改了它的类”是什么意思呢?从客户的视角来看:如果说你使用的对象能够完全改变它的行为,那么你会觉得,这个对象实际上是从别的类实例化而来的。然而,实际上,你知道我们是在使用组合通过简单引用不同的状态对象来造成类改变的假象
状态模式的类图
状态模式和策略模式的类图是一样的,下面会分析这两种模式的区别所在
糖果机器的例子
假设我们要实现一个含有JVM的糖果机器,糖果机共有5个状态:没有25分钱、有25分钱、售出糖果、糖果售罄、赢家状态,当顾客进行不同操作时,糖果机在不同条件下会进入不同状态,下面是糖果机器的状态转换图,这个状态转换图比较复杂是因为加入一个赢家状态,每当转动曲柄会有10%的几率进入赢家状态。赢家状态会多发放一颗糖果。
对于这种状态比较复杂的,我们如果还用条件判断来实现,那么转动曲柄部分的代码就会非常复杂
按照状态模式的基本思想来设计,可以分为以下三个步骤来实现这个糖果机器:
(1) 首先,我们定义一个state接口,在这个接口内,糖果机的每个动作都有一个对应的方法
(2)然后为机器的额每个状态实现状态类。这些类将负责在对应状态下进行机器的行为
(3)最后,我们要将动作委托到状态类
对应的java代码如下:
//State.java
package State;
/*
* 状态接口State
*/
public interface State {
public void insertQuarter(); // 投入25分钱
public void ejectQuarter(); // 拒绝25分钱
public void turnCrank(); // 转动曲柄
public void dispense(); // 发放糖果
}
//NoQuarterState.java
package State;
import State.GumballMachine;
/*
* 没有25分钱状态,实现了State接口
*/
public class NoQuarterState implements State{
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine){
this.gumballMachine=gumballMachine;
}
// 投入25分钱
public void insertQuarter() {
System.out.println("You insert a quarter");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
// 拒绝25分钱
public void ejectQuarter() {
System.out.println("You haven't insert a quarter");
}
// 转动曲柄
public void turnCrank() {
System.out.println("You turned crank,but you there's no quarter");
}
// 发放糖果
public void dispense() {
System.out.println("You need to pay first");
}
}
//HasQuarterState.java
package State;
import java.util.Random;
import State.GumballMachine;
/*
* 有25分钱状态,实现了State接口
*/
public class HasQuarterState implements State {
Random randomWinner = new Random(System.currentTimeMillis()); //首先我们增加一个随机数产生器,产生10%赢的机
会
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine){
this.gumballMachine = gumballMachine;
}
// 投入25分钱
public void insertQuarter() {
System.out.println("You can not insert anther quarter");
}
// 拒绝25分钱
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);
System.out.println("winner =" + winner);
if((winner ==0) && (gumballMachine.getCount() > 1)) {
gumballMachine.setState(gumballMachine.getWinnerState()); //如果赢了而且有足够的糖果进入 winnerstate状态
} else {
gumballMachine.setState(gumballMachine.getSoldState()); //否则进入soldstate状态
}
}
// 发放糖果
public void dispense() {
System.out.println("No gumball dispensed");
}
}
//SoldState.java
package State;
import State.GumballMachine;
/*
* 售出糖果状态,实现了State接口
*/
public class SoldState implements State{
GumballMachine gumballMachine;
public SoldState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
// 投入25分钱
public void insertQuarter() {
System.out.println("Please wait, we're already giving you a gumball");
}
// 拒绝25分钱
public void ejectQuarter() {
System.out.println("Sorry,you have already turn crank");
}
// 转动曲柄
public void turnCrank() {
System.out.println("trun twice ,doesn't give you anthor gamball!");
}
// 发放糖果
public void dispense() {
gumballMachine.releaseBall();
if(gumballMachine.getCount()>0){
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
System.out.println("Opps,out of gamball!");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
//SoldOutState.java
package State;
import State.GumballMachine;
/*
* 通过售罄状态,实现了State接口
*/
public class SoldOutState implements State{
GumballMachine gumballMachine;
public SoldOutState(GumballMachine gumballMachine){
this.gumballMachine=gumballMachine;
}
// 投入25分钱
public void insertQuarter() {
System.out.println("You can't insert a quarter, the machine is sold out");
}
// 拒绝25分钱
public void ejectQuarter() {
// TODO Auto-generated method stub
System.out.println("You can't eject, you haven't inserted a quarter yet");
}
// 转动曲柄
public void turnCrank() {
// TODO Auto-generated method stub
System.out.println("You turned, but there are no gumballs");
}
// 发放糖果
public void dispense() {
// TODO Auto-generated method stub
System.out.println("No gumball dispensed");
}
}
//WinnerState.java
package State;
import State.GumballMachine;
/*
* 赢家状态,实现了State接口
*/
public class WinnerState implements State{
GumballMachine gumballMachine;
public WinnerState(GumballMachine gumballMachine){
this.gumballMachine = gumballMachine;
}
// 投入25分钱
@Override
public void insertQuarter() {
// TODO Auto-generated method stub
System.out.println("Please wait, we're already giving you a gumball");
}
// 拒绝25分钱
@Override
public void ejectQuarter() {
// TODO Auto-generated method stub
System.out.println("Sorry,you have already turn crank");
}
// 转动曲柄
@Override
public void turnCrank() {
// TODO Auto-generated method stub
System.out.println("trun twice ,doesn't give you anthor gamball!");
}
// 发放糖果
@Override
public void dispense() {
// TODO Auto-generated method stub
System.out.println("You're a Winner! You get two gumballs for your quarter");
gumballMachine.releaseBall();
if(gumballMachine.getCount() == 0) {
gumballMachine.setState(gumballMachine.getSoldOutState());
} else {
gumballMachine.releaseBall();
if(gumballMachine.getCount()>0){
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
System.out.println("Opps,out of gamball!");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
}
//糖果机上下文环境类Java源文件 GumballMachine.java
package State;
import State.HasQuarterState;
import State.NoQuarterState;
import State.SoldOutState;
import State.SoldState;
import State.WinnerState;
import State.State;
/*
* 糖果机器上下文环境接口类 GumballMachine
*/
public class GumballMachine {
//状态实例
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State winnerState;
// 实例变量state,初始化为糖果售罄状态
State state = soldOutState;
// 记录机器内装有糖果的数目,开始机器是没有装糖果的
int count=0;
// 构造器取得糖果的初始数目并把它放在一个实例变量count中
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;
// 若超过0颗糖果,就将状态设置为NoQuarterState
if(numberGumballs > 0) {
state = noQuarterState;
}
}
// 取得机器内的糖果数目
public int getCount() {
return count;
}
// 取得糖果售罄状态
public State getSoldOutState() {
return soldOutState;
}
// 取得没有25分钱状态
public State getNoQuarterState() {
return noQuarterState;
}
// 取得拥有25分钱
public State getHasQuarterState() {
return hasQuarterState;
}
// 取得售出糖果状态
public State getSoldState() {
return soldState;
}
// 取得赢家状态
public State getWinnerState() {
return winnerState;
}
// 投入25分钱
public void insertQuarter(){
state.insertQuarter();
}
// 拒绝25分钱
public void ejectQuarter(){
state.ejectQuarter();
}
// 转动曲柄
public void turnCrank(){
state.turnCrank();
state.dispense();
}
// 设置状态
public void setState(State state){
this.state=state;
}
// 糖果滚出来一个
public void releaseBall(){
System.out.println("A gumball comes rolling out of the solt...");
if(count!=0){
count--;
}
}
}
//测试糖果机的Java源文件 GumballMachineTestDrive.java
package State;
/*
* 糖果机测试驱动程序:GumballMachineTestDrive.java
*/
public class GumballMachineTestDrive {
/**
* @param args
*/
public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine(5);
System.out.println(gumballMachine);
System.out.println("The current gumball number is:" + gumballMachine.getCount());
System.out.println("****************************************");
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
System.out.println("The current gumball number is:" + gumballMachine.getCount());
System.out.println("****************************************");
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
System.out.println("The current gumball number is:" + gumballMachine.getCount());
System.out.println("****************************************");
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
System.out.println("The current gumball number is:" + gumballMachine.getCount());
System.out.println("****************************************");
}
}
在糖果机增加每个状态的get 方法,并提供set方法,然后把动作委托给状态类来完成,这样我们就可以在状态类中通过set+get方法配合来实现状态之间的转换,例如gumballMachine.setState(gumballMachine.getSoldOutState());表明修改状态进入soldoutstate 状态.
将状态转换放在状态类中缺点是:状态之间产生了依赖,通过使用context上的 getter方法把依赖减到最少
状态模式要点
(1)客户不会和状态进行交互,全盘了解状态是 context的工作
(2)在状态模式中,每个状态通过持有Context的引用,来实现状态转移
(3)使用状态模式总是会增加设计中类的数目,这是为了要获得程序可扩展性,弹性的代价,如果你的代码不是一次性的,后期可能会不断加入不同的状态,那么状态模式的设计是绝对值得的。
(4)状态类可以被多个context实例共享
状态模式和策略模式对比
首先让我们来看看它们之间更多的相似之处:
- 添加新的状态或策略都很容易,而且不需要修改使用它们的Context对象。
- 它们都让你的代码符合OCP原则(软件对扩展应该是开发的,对修改应该是关闭的)。在状态模式和策略模式中,Context对象对修改是关闭的,添加新的状态或策略,都不需要修改Context。
- 正如状态模式中的Context会有初始状态一样,策略模式同样有默认策略。
- 状态模式以不同的状态封装不同的行为,而策略模式以不同的策略封装不同的行为。
- 它们都依赖子类去实现相关行为
两个模式的差别在于它们的”意图“不同:
状态模式帮助对象管理状态,我们将一群行为封装早状态对象中,context的行为随时可委托到那些状态中的一个.随着时间的流逝,当前状态在状态对象集合中游走改变,以反映context内部状态,因此,context的行为也会跟着改变。当要添加新的状态时,不需要修改原来代码添加新的状态类即可。
而策略模式允许Client选择不同的行为。通过封装一组相关算法,为Client提供运行时的灵活性。Client可以在运行时,选择任一算法,而不改变使用算法的Context。一些流行的策略模式的例子是写那些使用算法的代码,例如加密算法、压缩算法、排序算法。客户通常主动指定context所要组合的策略对象是哪一个,