什么是状态设计模式?
状态设计模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
为什么要使用状态设计模式?
状态设计模式可将对象的状态行为封装到单独的状态对象中,并将对象的动作委托到当前状态的对象。通过这样的方式实现封装变化、对扩展开放、对修改关闭以增强代码可读性、扩展性、维护性的目的。
状态设计模式怎么用?
试想下面这样一种需求。
万能糖果机公司委托我们通过 java 语言设计一种糖果机的控制程序,以实现类似如下的状态控制流程功能。
st=>start: 开始使用糖果机
op=>operation: 投入硬币
cond=>condition: coins > 25?
op2=>operation: 转动曲柄
op3=>operation: 售出糖果
op4=>operation: 退回投入的硬币
e=>end: 使用完毕
st->op->cond
cond(yes)->op2
cond(no)->op4
op2->op3->e
业务流程拥有至少3-4种可执行的动作分别是:投入硬币、退回硬币、转动曲柄、售出糖果。另外还具备3种(或者更多)状态对应可执行动作,它们分别是:等待投币、已投币、售罄状态。
糖果机的业务表面上看起来挺简单的。唯一需要注意的是每次执行某种动作之前,你必须对当前所处的状态进行检查。比如用户在投入硬币且消费硬币之前,无法再次投入硬币。
package designpattern.State;
/**
* 状态设计模式
*
* @author lincanhan
*/
public class GumballMachine {
// 售罄状态
final static int SOLD_OUT = 0;
// 已投币状态
final static int HAS_QUARTER = 2;
// 等待投币状态
final static int NO_QUARTER = 3;
static int state = SOLD_OUT;
static int count = 0;
public GumballMachine(int count) {
this.count = count;
if (count > 0) {
state = NO_QUARTER;
}
}
/**
* 投币函数
*/
public void insertQuarter() {
if (state == NO_QUARTER) {
System.out.println("投入硬币成功");
state = HAS_QUARTER;
} else if (state == SOLD_OUT) {
System.out.println("已售罄,退回硬币");
} else if (state == HAS_QUARTER) {
System.out.println("请勿重复投入硬币");
} else {
System.out.println("未知的状态错误");
}
}
/**
* 转动曲柄函数
*/
public static void turnCrank() {
if (state == NO_QUARTER) {
System.out.println("还未投入硬币");
state = HAS_QUARTER;
} else if (state == SOLD_OUT) {
System.out.println("已售罄。退回硬币");
} else if (state == HAS_QUARTER) {
System.out.println("正在发放糖果。。。。");
System.out.println("发放完毕");
state = NO_QUARTER;
} else {
System.out.println("未知的状态错误");
}
}
}
这不是一个完整的工业级的糖果机实现。不过在本篇文章中为了简略期间,这样一个简化版的糖果机就足够说明问题了。
现在你认为代码已经可以交付了。可惜该来的总会来。
万能糖果机公司的产品经理们十分满意我们的工作成果,并希望在糖果机中的流程中增加一个功能。他们希望糖果机在投币后有几率“中奖”,在稍后转动曲柄发放糖果时有几率发放出双倍的糖果以加强糖果机的娱乐性,继而提升销售量。
现在回过头来看看刚才的代码,并考虑如何对应这个变化。。。。
- 首先,需要增加一种状态叫做中奖。
- 需要在每个动作函数中增加中奖状态的 check。
- 在 turnCrank 函数中需要判断当前是否是中奖状态,若是则要发放两倍糖果。并且在最后都需要把状态极的状态还原到最初的状态。
随着业务复杂度的增加,所对应的状态和动作函数的增多,这样的变更简直是噩梦。而对于产品经理们来说这样的变更和其他千千万万个变更一样属于:【这个需求很简单,怎么实现我不管。明天上线】的范畴。
新的设计
显然又到了设计模式的救场的时候了。
于是新的设计思路如下:
- 封装变化。
- 将状态的行为封装到状态对象中。
- 利用组合行为将状态变化委托到每个状态行为对象中去执行。
依照这样的思路,我们先将状态等易变化的部分封装起来。
/**
* 状态设计模式
* 状态接口
*/
public interface State {
/**
* 投币函数
*/
void insertQuarter();
/**
* 转动曲柄函数
*/
void turnCrank();
}
/**
* 状态设计模式
* 已投入硬币,待出售状态
*/
public class HasQuarterState implements State {
private GumballMachine gumballMachine;
public GumballMachine getGumballMachine() {
return gumballMachine;
}
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void setGumballMachine(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("已投币,请先消费。");
}
@Override
public void turnCrank() {
System.out.println("发放糖果。。。");
System.out.println("糖果发放完毕");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
}
/**
* 状态设计模式
* 等待投入硬币状态
*/
public class NoQuarterState implements State {
private GumballMachine gumballMachine;
public GumballMachine getGumballMachine() {
return gumballMachine;
}
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void setGumballMachine(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("投入硬币成功");
gumballMachine.setState(getGumballMachine().getHasQuarterState());
}
@Override
public void turnCrank() {
System.out.println("请投入硬币");
}
}
/**
* 状态设计模式
* 售罄状态
*/
public class SoldOutState implements State {
private GumballMachine gumballMachine;
public GumballMachine getGumballMachine() {
return gumballMachine;
}
public SoldOutState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void setGumballMachine(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("已售罄");
}
@Override
public void turnCrank() {
System.out.println("已售罄");
}
}
然后利用委托方式将糖果机的任务委托给状态对象使用。
/**
* 状态设计模式
* 糖果机
*/
public class GumballMachine {
/**
* 糖果机所对应的所有状态
*/
private State hasQuarterState;
private State noQuarterState;
private State soldOutState;
/**
* 糖果机目前状态
*/
private State state;
/**
* 初始化糖果机状态
*/
public GumballMachine() {
hasQuarterState = new HasQuarterState(this);
noQuarterState = new NoQuarterState(this);
soldOutState = new SoldOutState(this);
this.state = noQuarterState;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public State getHasQuarterState() {
return hasQuarterState;
}
public void setHasQuarterState(State hasQuarterState) {
this.hasQuarterState = hasQuarterState;
}
public State getNoQuarterState() {
return noQuarterState;
}
public void setNoQuarterState(State noQuarterState) {
this.noQuarterState = noQuarterState;
}
public State getSoldOutState() {
return soldOutState;
}
public void setSoldOutState(State soldOutState) {
this.soldOutState = soldOutState;
}
/**
* 硬币投入动作函数
*/
public void insertQuarter() {
state.insertQuarter();
}
/**
* 转动曲柄函数
*/
public void turnCrank() {
state.turnCrank();
}
public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine();
gumballMachine.turnCrank();
System.out.println(gumballMachine.getState().getClass().getSimpleName());
gumballMachine.insertQuarter();
System.out.println(gumballMachine.getState().getClass().getSimpleName());
gumballMachine.turnCrank();
}
}
请投入硬币
NoQuarterState
投入硬币成功
HasQuarterState
发放糖果。。。
糖果发放完毕
大功告成。现在的设计不仅能帮你快速解决频繁变更的需求还能减少你和产品经理对话的时间!
这样的代码面向的是接口,而非具体类型编程。它们对扩展开放,对修改关闭。若发生需求变更,你仅仅需要的是增加一个状态对象,在状态对象处理好逻辑,而其他地方的代码你完全不需要改动。
总结
通过上面的示例,我们可以发现这又是一个利用组合来解决问题的设计模式。很显然组合要比继承更适合于扩展与维护。
等等,我为什么要说又?因为在之前的文章中,策略设计模式和状态设计模式非常相似,甚至他们的 uml 图都是一样的。那么为何状态设计模式要单独拿出来讲呢?
这是因为策略设计模式的使用目的在于对象主动地去控制算法或者行为,而状态设计模式更倾向于定义好一组可以互相转换的状态。虽然在设计上他们十分相似但是他们在使用上却有不同的目的。
网友评论