通过改变对象内部状态帮助对象控制行为
以一个简单例子说明,假设我们要模拟制造一台糖果机器,对方给你的机器流程图如下
ok,我们现在简单分析这张状态图,可将状态提取出来:有硬币,无硬币,售出糖果,糖果售空四个状态,行为动作提取出来:投入一个硬币,退回一个硬币,转动曲柄,发放糖果四个行为,当然还有一些特殊情况,具体情况参考代码
代码如下(参考)
GumballMachine
public class GumballMachine {
/**
* 状态模式 糖果机的状态都用一个不同整数表示
*
*/
final static int SOLD_OUT = 0;// 糖果售空
final static int NO_QUARTER = 1;// 没有投25分钱
final static int HAS_QUARTER = 2;// 投了25分钱
final static int SOLD = 3;// 糖果售出
// 当前状态
int state = SOLD_OUT;
// 用来追踪糖果数量
int count = 0;
public GumballMachine(int count) {
this.count = count;
if (count > 0) {
// 如果有糖果,机器变为没有投25分钱状态(待购买状态)
state = NO_QUARTER;
}
}
// 投入25分钱方法
public void insertQuarter() {
if (state == HAS_QUARTER) {
System.out.println("已经投过币了,不能再投了");
} else if (state == NO_QUARTER) {
state = HAS_QUARTER;
System.out.println("请投入一枚硬币");
} else if (state == SOLD_OUT) {
System.out.println("不能再投币了,机器已经售空了");
} else if (state == SOLD) {
System.out.println("请稍等,正在为你出糖果");
}
}
// 退出25分钱方法
public void ejectQuarter() {
// 1\当客户要退钱是
if (state == HAS_QUARTER) {
System.out.println("零钱退回");
state = NO_QUARTER;// 进入没投币状态
} else if (state == NO_QUARTER) {
System.out.println("你没有投入硬币");
} else if (state == SOLD) {
System.out.println("对不起,你已经转动了曲轴,无法退币了");
} else if (state == SOLD_OUT) {
System.out.println("糖果售空,无法退币");
}
}
// 转动曲轴方法(顾客)
public void turnCrank() {
if (state == HAS_QUARTER) {
System.out.println("请转动曲轴");
state = SOLD;// 状态变为售出状态
dispense();// 调用发放糖果方法
} else if (state == SOLD) {
System.out.println("转动两次也不给你糖果");
} else if (state == NO_QUARTER) {
System.out.println("请先投硬币");
} else if (state == SOLD_OUT) {
System.out.println("机器售空");
}
}
// 发放糖果方法
private void dispense() {
if (state == SOLD) {
System.out.println("一包糖果出来了");
count -= 1;
// 当糖果数量0时候呢?
if (count == 0) {
System.out.println("哎呀,糖果售空了~~");
state = SOLD_OUT;
} else {
state = NO_QUARTER;
}
} else if (state == NO_QUARTER) {
System.out.println("你需要投入硬币");
} else if (state == SOLD_OUT) {
System.out.println("没有糖果了");
} else if (state == HAS_QUARTER) {
System.out.println("没有糖果了");
}
}
// 重写toString()方法输入糖果机信息
@Override
public String toString() {
StringBuffer result = new StringBuffer();
result.append("欢迎使用糖果机\n");
result.append("糖果数量:" + count + "\n");
result.append("当前糖果机状态:" + state);
return result.toString();
}
}
TestMain
public class TestMain {
public static void main(String[] args) {
// 装了5个糖
GumballMachine gumballMachine = new GumballMachine(5);
System.out.println(gumballMachine + "\n");// 打印糖果机信息
gumballMachine.insertQuarter();// 投入硬币
gumballMachine.turnCrank();// 转动曲柄
System.out.println(gumballMachine + "\n");// 打印糖果机信息
gumballMachine.insertQuarter();// 投入硬币
gumballMachine.ejectQuarter();// 要求退币
gumballMachine.turnCrank();// 转动曲柄,拿不到糖果
System.out.println(gumballMachine + "\n");// 打印糖果机信息
gumballMachine.insertQuarter();// 投入硬币
gumballMachine.turnCrank();// 转动曲柄(拿到糖果)
gumballMachine.insertQuarter();// 投入硬币
gumballMachine.turnCrank();// 转动曲柄(拿到糖果)
gumballMachine.ejectQuarter();// 要求机器退钱
System.out.println(gumballMachine + "\n");// 打印糖果机信息
gumballMachine.insertQuarter();// 投入硬币
gumballMachine.insertQuarter();// 投入硬币
gumballMachine.turnCrank();// 转动曲柄(拿到糖果)
// 下面开始压力测试
//gumballMachine.insertQuarter();// 投入硬币
//gumballMachine.turnCrank();// 转动曲轴
//gumballMachine.insertQuarter();// 投入硬币
//gumballMachine.turnCrank();// 转动曲轴
System.out.println(gumballMachine + "\n");// 打印糖果机信息
}
}
效果图(无压力测试)
效果图(压力测试)
效果可以快速实现,但这样的代码显然还有不足之处,假设有个新的需求:增加一个幸运用户,就是购买者有10%的概率可以一次买到两颗糖果,这又要如何实现呢?先看流程图
按照之前的1.0代码加新中奖者功能,显然不合适,要在每个方法里写入,显然太麻烦,这里就需要重构代码了。我们可以试着行为封装起来,也可以把糖果机器具体一下.我们要做的是如下:
1、首先定义一个State接口,在这个接口内,糖果机的每个动作都有一个对应的方法。
2、然后为机器中的每个状态实现类。这些类将负责在对应的状态下进行机器的行为。
3、重构旧代码,取而代之方式是将动作委托给状态类。
具体实现请看代码
结构图
State
package Interface;
public interface State {
/**
* 将四种状态抽象出来成基类
*/
// 投入硬币
public void insertQuarter();
// 退回硬币
public void ejectQuarter();
// 转动曲柄
public void turnCrank();
// 发放糖果
public void dispense();
}
GumballMachine
package Machine;
public class GumballMachine {
/**
* 不在使用静态整数,都是用对象
*
*/
State soldOutState;// 糖果售空
State noQuarterState;// 没有投币
State hasQuarterState;// 投了币
State soldState;// 糖果售出
State winnerState;// 中奖状态
// 当前状态,持有的是(糖果售空)对象
State state = soldOutState;
int count = 0;
// numberGumball构造器取得糖果的初始数目后,并把它存放在一个实例变量中
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;
if (numberGumballs > 0) {
state = noQuarterState;
}
}
/**
* 机器的各个操作不在这里具体实现了 而是丢给接口,再让具体实现类去实现接口
*/
// 添加硬币
public void insertQuarter() {
state.insertQuarter();
}
// 退出硬币
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("一包糖果出来了");
if (count != 0) {
count = count - 1;
}
}
public int getCount() {
return count;
}
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("欢迎使用糖果机\n");
result.append("糖果数量:" + count + "\n");
result.append("当前糖果机状态: " + state + "\n");
return result.toString();
}
}
各个行为类实现
HasQuarterState
package State_Implements;
public class HasQuarterState implements State {
/**
* 投币的实现类
*
* @param gumballMachine
*/
// 增加一个随机数产生器,10%机会
Random randomWinner = new Random(System.currentTimeMillis());
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("你已经投过币了,不能再投了");
}
public void ejectQuarter() {
System.out.println("硬币退出");
// 退出硬币后,状态变为没有硬币(待购买)状态
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
public void turnCrank() {
System.out.println("转动.....");
int winner = randomWinner.nextInt(10);// 产生0-9随机数,当是0并且还有糖果的时候中奖了
if ((winner == 0) && (gumballMachine.getCount() > 1)) {
gumballMachine.setState(gumballMachine.getWinnerState());
} else {
gumballMachine.setState(gumballMachine.getSoldState());
}
}
public void dispense() {
System.out.println("没有糖果出来");
}
public String toString() {
return "等待使用曲柄";
}
}
NoQuarterState
package State_Implements;
/**
* 没有投币状态实现,
* 都通通要实现状态基类
* @author Joy
*
*/
public class NoQuarterState implements State {
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("你投了一个硬币");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
public void ejectQuarter() {
System.out.println("你没有投币,无法退币~~");
}
public void turnCrank() {
System.out.println("你没有投币,无法继续~~");
}
public void dispense() {
System.out.println("你需要投币才能买糖果");
}
public String toString() {
return "正在运营";
}
}
SoldOutState
package State_Implements;
/**
* 售空状态
*
* @author Joy
*
*/
public class SoldOutState implements State {
GumballMachine gumballMachine;
public SoldOutState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("抱歉,你不能在投币了,糖果售空了");
}
public void ejectQuarter() {
System.out.println("抱歉,糖果售空,无法退币");
}
public void turnCrank() {
System.out.println("抱歉,转动曲柄无效,糖果售空了");
}
public void dispense() {
System.out.println("糖果售空了");
}
public String toString() {
return "糖果售空";
}
}
SoldState
package State_Implements;
/**
* 卖出糖果状态
*
* @author Joy
*
*/
public class SoldState implements State {
GumballMachine gumballMachine;
public SoldState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("请稍等,糖果正出货");
}
public void ejectQuarter() {
System.out.println("抱歉,你已使用曲柄,无法退币");
}
public void turnCrank() {
System.out.println("曲柄不可重复使用");
}
public void dispense() {
// 调用糖果出货方法
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0) {
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
System.out.println("哎呀,糖果售空了");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
public String toString() {
return "一个糖果已出货";
}
}
WinnerState
package State_Implements;
public class WinnerState implements State {
GumballMachine gumballMachine;
public WinnerState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("不能投币");
}
public void ejectQuarter() {
System.out.println("不能投币");
}
public void turnCrank() {
System.out.println("不能使用曲柄");
}
public void dispense() {
System.out.println("恭喜中奖了,你得到两个糖果~~");
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("哎呀,糖果售空了");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
public String toString() {
return "你是中奖者,得到两个糖果";
}
}
GumballMachineTestDrive 测试类
package TestMain;
import Machine.GumballMachine;
public class GumballMachineTestDrive {
public static void main(String[] args) {
// 一开始5颗糖
GumballMachine gumballMachine = new GumballMachine(5);
System.out.println(gumballMachine + "\n");
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine + "\n");// 输出状态
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine + "\n");// 输出状态
}
}
效果图
状态模式定义:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
注:这个模式是将状态封装成为独立地类,并将动作委托给代表当前状态的对象。
状态模式类图如下,后来本人在回顾时发现状态模式和策略模式类图很相似,有兴趣朋友可以将两者去比较不同
感谢你看到这里,状态模式到这里就结束了,本人文笔随便,若有不足或错误之处望给予指点,90度弯腰~~~很快我会发布下一个设计模式的内容,生命不息,编程不止!
参考书籍:《Head First 设计模式》
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。