状态模式:允许对象在状态改变时改变它的行为,适用于一个任务有多种状态及多种动作时。
示例演示实现一个自动糖果机,其状态图是这个样子的:
这个状态图展现了糖果机可以进行的五个动作和四种状态,基本思路是将状态抽象成一个超类或者接口,然后这个超类或接口包含所有这些动作,糖果机拥有不同状态的实现,这些不同的实现能正确处理在这种状态下某个动作并将糖果机的状态切换到变化后的状态。好吧,说了这么多,如果不理解就直接看代码吧。
这里我们用接口的方式实现,首先是状态的接口
package com.lttclaw.stateMode;
public interface State {
/**
* 投币
*/
public void insertQuarter();
/**
* 退币
*/
public void ejectQuarter();
/**
* 转动曲柄
*/
public void turnCrank();
/**
* 发放糖果
*/
public void dispense();
}
然后看看糖果机:
package com.lttclaw.stateMode;
public class GumballMachine {
private State noQuarterState;
private State hasQuarterState;
private State soldState;
private State soldOutState;
private State currentState;
private int count=0;
public GumballMachine(int count){
this.count=count;
noQuarterState=new NoQuarterState(this);
hasQuarterState=new HasQuarterState(this);
soldState=new SoldState(this);
soldOutState=new SoldOutState(this);
if(count>0){
setState(noQuarterState);
}else{
setState(soldOutState);
}
}
public void insertQuarter(){
currentState.insertQuarter();
}
public void ejectQuarter(){
currentState.ejectQuarter();
}
public void turnCranck(){
currentState.turnCrank();
currentState.dispense();
}
/**
* 不开放的方法,对包外的客户不提供此方法
*/
void releaseBall(){
System.out.println("发糖糖啦");
if(count>0){
count--;
}
}
int getCount(){
return count;
}
void setState(State state){
currentState=state;
}
State getNoQuarterState() {
return noQuarterState;
}
State getHasQuarterState() {
return hasQuarterState;
}
State getSoldState() {
return soldState;
}
State getSoldOutState() {
return soldOutState;
}
State getCurrentState() {
return currentState;
}
}
然后我们看看四种状态是如果实现各自的动作的,先是未投币状态,即有库存的初始状态:
package com.lttclaw.stateMode;
public class NoQuarterState implements State {
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine=gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("你刚刚投了币");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
@Override
public void ejectQuarter() {
System.out.println("呃,没投币啊,我怎么退给你??");
}
@Override
public void turnCrank() {
System.out.println("不掏钱是没有糖滴");
}
@Override
public void dispense() {
}
}
然后是投币后状态,即投币后还未拉杆取糖:
package com.lttclaw.stateMode;
public 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("退币了,接着,biubiu");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
@Override
public void turnCrank() {
System.out.println("好,下面将为你发糖糖");
gumballMachine.setState(gumballMachine.getSoldState());
}
@Override
public void dispense() {
}
}
然后看看卖出状态,这个状态不是持续状态,只会在用户取糖后短暂进入,然后很快就会根据库存进入下个状态:
package com.lttclaw.stateMode;
/**
* @author claw
* 售出糖果状态
*
*/
public 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{
System.out.println("最后一颗也卖掉了,现在没啦");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
再看售罄状态:
package com.lttclaw.stateMode;
public class SoldOutState implements State {
GumballMachine gumballMachine;
public SoldOutState(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() {
}
}
好了,下面就可以来试试我们的糖果机了,这个测试类我写在另一个包里,否则客户就可以随意调用我们的内部业务方法了……
package com.lttclaw.client;
import com.lttclaw.stateMode.GumballMachine;
public class Client {
public static void main(String[] args) {
GumballMachine gumballMachine=new GumballMachine(4);
gumballMachine.insertQuarter();
gumballMachine.turnCranck();
gumballMachine.insertQuarter();
gumballMachine.insertQuarter();
gumballMachine.ejectQuarter();
gumballMachine.ejectQuarter();
gumballMachine.turnCranck();
for(int i=0;i<5;i++){
gumballMachine.insertQuarter();
gumballMachine.turnCranck();
}
}
}
运行结果如下:
你刚刚投了币
好,下面将为你发糖糖
发糖糖啦
你刚刚投了币
已经有币了,先用掉再投吧
退币了,接着,biubiu
呃,没投币啊,我怎么退给你??
不掏钱是没有糖滴
不掏钱是没有糖滴
你刚刚投了币
好,下面将为你发糖糖
发糖糖啦
你刚刚投了币
好,下面将为你发糖糖
发糖糖啦
你刚刚投了币
好,下面将为你发糖糖
发糖糖啦
最后一颗也卖掉了,现在没啦
抱歉,已经卖完了,自动退币给您
卖完了,摇坏也没有了
抱歉,已经卖完了,自动退币给您
卖完了,摇坏也没有了
下面总结一下,状态模式允许对象在内部状态改变时改变它的行为,使之看起来好像变成了另一个类。用这种方法实现有较好的可扩展性,如果要加动作,则为接口添加方法并为每个实现类实现此方法即可,如果要加状态,则添加一个实现接口的类即可。其类图大概长这样: