状态模式,我们给他一个通俗的定义:在我们改变某一个对象的内部状态的时候,我们也要同时改变它的行为,表面上是改变了动作和行为,实际上则是改变了类的内部的状态。通过这个定义我们可以隐约的猜想到,我们在改变的状态的时候,其实是改变的一个类,也就是说一个类封装了一个状态,这个思想和策略模式比较像,将变化的封装起来,在策略模式中封装的是一个算法,以及算法的流程,其算法的大致过程一致,只是具体到某一个算法时会进行区分,可以看到这个组合的思想也能在状态模式中体现出来。类似于下面的表达:
State:实际上定义了一个所有具体状态的共同接口,任何状态都是用这个接口,这样状态之间就可以互相替换。ConcreteA和ConcreteB两个处理来自Context的请求,每一个ContextState都提供了他自己对请求的实现
考虑下面一个例子:假如有一个糖果机,一个糖果的价格是25美分,糖果机默认状态是没有25美分的状态,当向糖果机中投入25美分后,我们就可以从糖果机的曲柄转动得到一个糖果,此时就出售了糖果,而当糖果机中没有糖果时,就会发出糖果售罄的状态,同时退换投入的25美分。我们发现在这个逻辑过程中,除了动作外,状态也是会发生改变的,所以,综合分析来看,我们有5个状态:投入25美分,退回25美分,转动曲柄,发放糖果,糖果售罄。分析后我们首先用类图设计,然后用代码来表示:
类图设计为:
实现为(完整代码地址为:GOGOGO):
/**
* @author Tianlong Zhang
* @time 2018/9/3 10:17
* @project DesignPattern
* @Version 1.0.0
*/
public class GumballMachine {
//定义状态字段
/*糖果售罄*/
final static int SOLD_OUT = 0;
/*没有投入25美分,默认初始状态*/
final static int NO_QUARTER = 1;
/*投入25美分*/
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("You can't insert another quarter");
}else if(state == NO_QUARTER){
state = HAS_QUARTER;
System.out.println("You insert 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 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();
}
}
可以用下面的代码进行测试:
/**
* @author Tianlong Zhang
* @time 2018/9/3 10:39
* @project DesignPattern
* @Version 1.0.0
*/
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 insert 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 insert 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 insert a quarter
You turned...
A gumball comes rolling out the slot
You insert 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 insert a quarter
You can't insert another quarter
You turned...
A gumball comes rolling out the slot
You insert 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
然而当我们还是面对着老话题,躲不开的话题,需求更改时,这样的设计方式就会显得捉襟见肘:此时的糖果公司由于竞争,需要给糖果机增加一个功能,每购买糖果10次,就可以获得一次竞赛的机会,出售糖果状态下会有10%的概率掉下两个糖果,所以对照我们上面的设计,是不是需要改动很多呢,不仅仅需要改动与竞赛相关的代码,原有的每个动作代码了里面的逻辑过程也都需要修改,因为此时有加入了一个新的状态,如果事件允许的话,可以自行尝试以下,这里就不再赘述了。
但是当我们换一种思考方式,类似于类的成员函数等与这个类的关系,本质上,类是定义了一个物体,而成员函数则大部分情况下为定义的这个物体的相关的动作。我们可以类比推理过来,可以将每个状态单独封装称一个类,用这个类来管理,这样就可以方便的管理状态,同时i也放也方便这个代码的维护和扩充,让我们不再去修改每部分代码的逻辑,住去修改相关,添加或删除相关的逻辑代码就可以了。所以我们放弃维护之前写过的代码,重新重构一个按照上述过程的代码:
首先定义一个接口,它规范了糖果机的行为,定义如下:
/**
* @author Tianlong Zhang
* @time 2018/9/3 10:43
* @project DesignPattern
* @Version 1.0.0
*/
public interface State {
public void insertQuarter();
public void ejectQuarter();
public void turnCrank();
public void dispense();
public void refill();
}
因为每个动作都会使得状态发生改变,所以我们需要对每个状态分开来管理,使得尽量一个类管理一个状态,这样我们分别构建类4个类:
以出售状态为例,继承定义的接口,实现行为,完成相关的逻辑:
**
* @author Tianlong Zhang
* @time 2018/9/3 10:45
* @project DesignPattern
* @Version 1.0.0
*/
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";
}
}
类似的,根据业务的逻辑进行编写,分别完成4个状态:分别为:售罄(SoldOutState),没有投入25美分(NoQuarterState),投入了25美分(HasQuarterState),竞猜成功状态(WinnerState)。这里就不一样赘述,详细可以参考完整代码 GOGOGO。
编写一个测试类进行测试:
/**
* @author Tianlong Zhang
* @time 2018/9/3 14:53
* @project DesignPattern
* @Version 1.0.0
*/
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();
······
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...
You inserted a quarter
You turned...
A gumball comes rolling out the slot...
Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 6 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: 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