状态模式(State)-老孙七十二变
俺老孙来也…我是如来佛祖玉皇大帝观音菩萨指定取西经特派使者花果山水帘洞美猴王齐天大圣孙悟空啊!老孙的必杀技就是七十二变,没错比老外的变相怪杰还有牛逼许多.
关于老孙我以前的光辉史,长得帅的都知道,什么长生不老之术,上天下地…憋提了,憋提了那都是过去,到了21世纪,老孙已经不叫什么齐天大圣了,只是小公司里的一只程序猿,有事没事敲敲代码压压惊.
好了不扯,咱们进入主题…[斜眼笑]
其实也没走题,讲的还是齐天大圣,看看他的特殊技能七十二变,如果看成是一个项目里需求,我们如果要完成的话,那岂不是要做72次判断和每次变身的处理.
分析72变
对于72变的学术研究就不太深入了,简单描述变法大致分为水陆空这几种方式.
- 水
- 水上:小船..
- 水下:鱼儿..
- 陆
- 陆上:猴子..
- 陆下:蚯蚓..
- 空
- 空中:小鸟..
- 空外:飞机..
估计我写的是假的悟空,还有变蚯蚓的,估摸大圣会吐槽我的.没关系,这不是重点,关键的是当我们如果要写下这段代码逻辑的话会是怎样?
我们先看看普通方式的写法.
public class TestClient {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请告诉悟空想变的东西...");
String things = sc.next();
System.out.println("悟空准备变: " + things);
// 处理变身的方法
handleState(things);
}
private static void handleState(String things) {
// 水里的类型
if ("小船".equals(things) || "鱼儿".equals(things)) {
// 水上
if ("小船".equals(things)) {
System.out.println("悟空变水面上的.." + things);
// 水下
} else if ("鱼儿".equals(things)) {
System.out.println("悟空变水里的.." + things);
}
// 陆地的类型
} else if ("猴子".equals(things) || "蚯蚓".equals(things)) {
if ("猴子".equals(things)) {
System.out.println("悟空变陆地上的.." + things);
} else if ("蚯蚓".equals(things)) {
System.out.println("悟空变陆地下的.." + things);
}
// 天空的类型
} else if ("小鸟".equals(things) || "飞机".equals(things)) {
if ("小鸟".equals(things)) {
System.out.println("悟空变天空中的.." + things);
} else if ("飞机".equals(things)) {
System.out.println("悟空变天空外的.." + things);
}
// 水陆空之外的范围变不了
} else {
System.out.println("变身失败...无此状态:" + things);
}
}
}
输出结果:
请告诉悟空想变的东西…
鱼儿
悟空准备变: 鱼儿
悟空变水里的..鱼儿
代码的坏味道
有没看到handleState(state)
这个方法实在太长了,如果悟空后面又学习了新的变法,可以变水,变苹果等之类的呢,那这个方法不是要逆天啦.在<重构>中提过,过长的方法会有坏味道.面向对象的设计就是希望做到代码的责任分解,因此对于这种条件判断if-else
,switch
过于冗杂的逻辑,可以使用状态设计模式
来解决.
来一点理论
理论说多了会困,少许理论也可怡情.
<大话设计模式>说到状态模式
主要解决的是当控制一个对象状态转换条件表达式过于复杂的情况.把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化.
通过状态模式来优化代码
说了那么多那么,那到底使用状态模式优化后效果如何呢,下面就是见证奇迹的时候..
- 每个状态的接口
/**
* 悟空变身后的不同状态的接口
*
* @author relicemxd
*
*/
public interface State {
/**
* 处理变身状态
*/
void handleState();
}
- 接口的具体实现类.
陆地状态的实现类,这里我定义了两种类型.
/*
* 悟空变身的陆地状态类
*/
public class LandState implements State {
public static final String MONKEY_STATE = "猴子";
public static final String EARTHWORM_STATE = "蚯蚓";
private String state;
/*
* 要陆地上的什么动物需要告诉悟空
*/
public LandState(String state) {
this.state = state;
}
/*
* 变身的方法,由悟空来调用.
* 悟空知道要变的类型才可以变身成功
*/
@Override
public void handleState() {
if (MONKEY_STATE.equals(state)) {
System.out.println("变 猴子....");
} else if (EARTHWORM_STATE.equals(state)) {
System.out.println("变 蚯蚓....");
}else{
//如果不符合条件则变身失败.
System.out.println("变身失败...");
}
}
}
海里状态的实现类,这里我也定义了两种类型.
/*
* 悟空变身的海里状态类
*/
public class SeaState implements State {
public static final String SHIP_STATE = "小船";
public static final String FISH_STATE = "鱼儿";
private String state;
public SeaState(String state) {
this.state = state;
}
@Override
public void handleState() {
if (FISH_STATE.equals(state)) {
System.out.println("变 鱼儿....");
} else if (SHIP_STATE.equals(state)) {
System.out.println("变 小船儿....");
}else{
//如果不符合条件则变身失败.
System.out.println("变身失败...");
}
}
}
空中状态的实现类,这里我也定义了两种类型.
/**
* 悟空变身的天空状态类
*
*/
public class SpaceState implements State {
public static final String BIRD_STATE = "鸟";
public static final String AIRPLANE_STATE = "飞机";
private String state;
public SpaceState(String state) {
this.state = state;
}
@Override
public void handleState() {
if (BIRD_STATE.equals(state)) {
System.out.println("变 小鸟....");
} else if (AIRPLANE_STATE.equals(state)) {
System.out.println("变 飞机....");
}else{
//如果不符合条件则变身失败.
System.out.println("变身失败...");
}
}
}
- 处理不同state的管理类,维护着
SeaState
,LandState
,SpaceState
三个状态的操作.
/*
* 这个Context类相当于 悟空
*
*/
public class Context {
private State state;
/**
* 悟空会根据你不同的状态而做不同的处理,
* 而处理的东西都可以在实现类里完成,如在变小船状态时做什么.
* 这样就便于拓展了.
* @param state
*/
public void setState(State state) {
this.state = state;
System.out.println("悟空准备开始变身..");
//悟空可以调用该变身方法
state.handleState();
}
}
显然上面的类多了很多,但是每个类里处理的行为也清晰了很多.有取也有舍.
如何使用呢?
对这个这些变身的行为进行归类和封装后,我们就可以在Client
中很方便的去调用了.
public class Client {
public static void main(String[] args) {
//Context 悟空类
Context context = new Context();
//陆地状态切换
//告诉悟空你想让他变成陆地上的蚯蚓.
LandState landState = new LandState(LandState.EARTHWORM_STATE);
//悟空得到需求准备变身..
context.setState(landState);
//水里状态切换
SeaState seaState = new SeaState(SeaState.SHIP_STATE);
context.setState(seaState);
//空中状态切换
//此时我想让悟空变成空中的小船,因此会变身失败.信息来源不对
SpaceState spaceState = new SpaceState(SeaState.SHIP_STATE);
context.setState(spaceState);
}
}
状态模式的组成
上下文类(Context): 在这里他就相当于悟空
,可以在该类中定义一些悟空
需要的行为.该类的主要任务是维护State
接口的一些子类的实例,如LandState
.
抽象状态类(State): 所有状态的共性接口,封装了与Context
相关的一些状态行为,如handleState(state)
.
具体状态类(LandState): State
接口的具体实现,对不同状态的行为处理子类.
看下UML类图
应用场景
- 电梯运行状态
- 维修,正常,自动关门,自动开门,消防..
- 红绿灯状态
- 红灯,绿灯,黄灯
订单状态
- 下单
- 已付款
- 已发货
- 送货中
- 已收货
- 售后
对于应用场景还有很多,只要你觉得一个实体有多个状态需要处理的,就可以通过state模式来设计代码.
总结
状态模式的优点:
- 意图清晰
将每一个状态转换和动作封装到一个类中,就把着眼点从执行状态提高到整个对象的状态. - 代码结构化
State模式提供了一个更好的方法来组织与特定状态相关的代码. - 便于维护
State模式将if else语句或switch case语句进行了封装,这样对于后续的增删改查从显示的处理数据进而转变成隐式的处理State的子类,减低了复杂逻辑的维护成本.
- 意图清晰
状态模式的缺点:
- 增加系统类和对象的个数,这是大多数设计模式的缺点.
- 对于代码不规范的同学,如果使用不当将导致程序结构和代码的混乱,主要是State模式结构与实现都较为复杂.