学好设计模式防被祭天:状态模式
为了防止被“杀”了祭天,学点设计模式,并总结下还是有必要的。
一:理解
- 状态模式从另一个角度思考状态转移问题。
- 原有逻辑是实体的状态从A变成B。在状态模式中,状态转义过程被抽象成从处于状态A的实体变成处于状态B的实体。
- 可以抽象出多个包含实体的状态类。
二:例子
你是个富二代。
你的生活状态很简单,一种是充满能量状态ENERGETIC_STATE,还有一种就是贤者时间状态XIANZHE_STATE。
当你在贤者时间时,只要休息了,就会变得活力满满。
当你在充满能量状态时,只要papapa了,就会进入贤者时间。
当你在贤者时间,就没有兴趣接着再papapa了。
当你活力满满,就不需要再休息了。
于是,你叫来程序员小菜帮你抽象一下你的生活状态。
小菜上来就是一顿敲。
@Data
public class FuErDai {
private static final int XIANZHE_STATE = 0;
private static final int ENERGETIC_STATE = 1;
private int state;
public FuErDai(int state) {
this.state = state;
}
public void rest() {
if (state == XIANZHE_STATE) {
System.out.println("当前状态是贤者时间,需要休息!");
state = ENERGETIC_STATE;
} else if (state == ENERGETIC_STATE) {
System.out.println("当前状态是能量满满,不需要休息!");
}
}
public void papapa() {
if (state == XIANZHE_STATE) {
System.out.println("当前状态是贤者时间,不想papapa!");
} else if (state == ENERGETIC_STATE) {
System.out.println("当前状态是能量满满,来一发!");
state = XIANZHE_STATE;
}
}
}
这是一个很简单的程序,FuErDai类中含有状态属性state,每次想要休息rest或者papapa时,使用if else对当前状态进行判断。
测试程序:
public class ClientV1 {
public static void main(String[] args) {
FuErDai fuErDai = new FuErDai(0);
fuErDai.papapa();
fuErDai.papapa();
fuErDai.rest();
fuErDai.rest();
fuErDai.papapa();
fuErDai.rest();
}
}
输入/输出:
当前状态是贤者时间,不想papapa!
当前状态是贤者时间,不想papapa!
当前状态是贤者时间,需要休息!
当前状态是能量满满,不需要休息!
当前状态是能量满满,来一发!
当前状态是贤者时间,需要休息!
这段程序简单易懂,你很开心。
有一天,你突然发现,自己在充满能量和贤者时间以外还有一个状态,就是一半一半状态HALF_STATE。
在一半一半状态时候:
- 你尝试休息,会有百分之五十的概率开始休息,休息完进入活力满满状态。
- 你尝试papapa,会有百分五十的概率接受约X,papapa之后进入贤者时间。
小菜觉得不就是多了一个状态而已,分分钟搞定。
@Data
public class FuErDaiV2 {
private static final int XIANZHE_STATE = 0;
private static final int ENERGETIC_STATE = 1;
private static final int HALF_STATE = 2;
private int state;
public FuErDaiV2(int state) {
this.state = state;
}
public void rest() {
if (state == XIANZHE_STATE) {
System.out.println("当前状态是贤者时间,需要休息!");
state = ENERGETIC_STATE;
} else if (state == ENERGETIC_STATE) {
System.out.println("当前状态是能量满满,不需要休息!");
} else if (state == HALF_STATE) {
if (isAccept()) {
System.out.println("当前状态是一半一半,可以休息");
state = ENERGETIC_STATE;
} else {
System.out.println("当前状态是一半一半,但不休息");
}
}
}
public void papapa() {
if (state == XIANZHE_STATE) {
System.out.println("当前状态是贤者时间,不想papapa!");
} else if (state == ENERGETIC_STATE) {
System.out.println("当前状态是能量满满,来一发!");
state = XIANZHE_STATE;
} else if (state == HALF_STATE) {
if (isAccept()) {
System.out.println("当前状态是一半一半,可以papapa");
state = XIANZHE_STATE;
} else {
System.out.println("当前状态是一半一半,但不papapa");
}
}
}
private boolean isAccept() {
return new Random().nextBoolean();
}
}
小菜在rest和papapa方法中增加了对HALF_STATE状态的判断,多了一层if else逻辑。
你觉得这个程序没问题,就向你的二代朋友们炫耀。
然而,这些朋友纷纷表示不屑。
虽然说不出为什么,但总觉得这么多if else不是很优雅。
炫耀不成反丢脸,你拿起你那把40米的大刀准备杀了这位程序员祭天。
40米的大刀吓得小菜急忙开始对于重构的思考:
- 这个程序主要抽象的是富二代状态的转移,可以尝试使用状态模式。
- 太多的if else的确不利于代码的维护,而且说不准哪天富二代又需要增加状态。
于是,小菜决定使用状态模式进行尝试。
他先抽象出一个接口,接口描述的是某状态下的富二代,无论处于什么状态的富二代,都有rest和papapa方法,通过State接口进行约束。
public interface State {
void rest();
void papapa();
}
接着,小菜新建了三个状态类。
状态类中包含FuErDaiV3属性,因为状态类其实表示的是当前状态下的富二代,rest和papapa方法,还是需要富二代来执行。
// 贤者时间状态
public class XianZheState implements State {
FuErDaiV3 fuErDaiV3;
public XianZheState(FuErDaiV3 fuErDaiV3) {
this.fuErDaiV3 = fuErDaiV3;
}
@Override
public void rest() {
System.out.println("当前状态是贤者时间,需要休息!");
fuErDaiV3.setState(fuErDaiV3.getEnergeticState());
}
@Override
public void papapa() {
System.out.println("当前状态是贤者时间,不想papapa!");
}
}
// 活力满满状态
public class EnergeticState implements State {
private FuErDaiV3 fuErDaiV3;
public EnergeticState(FuErDaiV3 fuErDaiV3) {
this.fuErDaiV3 = fuErDaiV3;
}
@Override
public void rest() {
System.out.println("当前状态是能量满满,不需要休息!");
}
@Override
public void papapa() {
System.out.println("当前状态是能量满满,来一发!");
fuErDaiV3.setState(fuErDaiV3.getXianZheSate());
}
}
// 一半一半状态
public class HalfState implements State {
private FuErDaiV3 fuErDaiV3;
public HalfState(FuErDaiV3 fuErDaiV3) {
this.fuErDaiV3 = fuErDaiV3;
}
@Override
public void rest() {
if (isAccept()) {
System.out.println("当前状态是一半一半,可以休息");
fuErDaiV3.setState(fuErDaiV3.getEnergeticState());
} else {
System.out.println("当前状态是一半一半,但不休息");
}
}
@Override
public void papapa() {
if (isAccept()) {
System.out.println("当前状态是一半一半,可以papapa");
fuErDaiV3.setState(fuErDaiV3.getXianZheSate());
} else {
System.out.println("当前状态是一半一半,但不papapa");
}
}
private boolean isAccept() {
return new Random().nextBoolean();
}
}
因为状态各不同,所以不同状态类对于rest和papapa方法的处理也不一样。
接下来是重构之后的富二代类FuErDaiV3。
@Data
public class FuErDaiV3 {
private State xianZheSate;
private State energeticState;
private State halfState;
private State state;
public FuErDaiV3() {
xianZheSate = new XianZheState(this);
energeticState = new EnergeticState(this);
halfState = new HalfState(this);
state = xianZheSate;
}
public void rest() {
state.rest();
}
public void papapa() {
state.papapa();
}
}
富二代类包含三个已知可能会进入的状态,和一个当前状态属性state。
在构造器新建富二代对象时,需要将本身传入状态类中,分别新建出处于贤者时间的高富帅,处于活力满满状态的高富帅和处于一半一半状态的高富帅。
并且将当前状态/初始状态设置成处于贤者时间的高富帅。
当你需要执行rest和papapa方法时,只需直接调用state的对应方法即可。
在使用了状态模式之后,除了判断百分之五十概率处用了if else,其余的条件判断语句都已被去掉。
当富二代需要增加状态时候,只需新建状态类,以及略微修改富二代类即可。
你很满意,于是唱起了hip-hop。
hip-hop三:再理解
- 状态模式中,富二代类包含状态属性,状态类中又包含富二代属性。相互引用,不知道该先写哪个类。
- 状态模式可以去掉对于当前状态的判断语句,方便日后的维护。
- 使得富二代的代码做到最清晰。
- 不能做到无修改扩展,当增加新状态时候,需为富二代类增加状态属性,并修改构造器。此外,还需要确认其他状态类在执行方法时,是否会进入新状态。不过总体而言,比使用状态模式之前还是优雅太多了。