在实际业务开发过程中,我们经常会遇到对应记录多种状态的变更和流转,一般情况下我们简单的可以用多个if来进行过程代码的编写,即if(状态符合){执行后续流程} if(状态不符合){return;}。但是实际上我们可以通过状态模式这一种较为巧妙的设计模式来达到我们的效果。
一、什么是状态模式
状态这个词汇我们并不陌生,在日常生活中,不同时间就有不同的状态,早上起来精神饱满,中午想睡觉,下午又渐渐恢复,晚上可能精神更旺也可能耗费体力只想睡觉,这一天中就对应着不同的状态。
但是我们不可能从早上直接跳过中午来到下午,也不可能直接跳过下午来到晚上。
我们需要一个能够定义出各个状态下可以做什么动作的业务模型,比如早上吃早餐,中午吃午餐,晚上吃晚餐。根据这个模型进行后续的状态设计,保证各个状态互不干扰。
二、模型分析
按照上述场景,我们抽象出一个可用的模型。
-
抽离状态
在该业务场景下,我们定义三个状态分别为早上(morning),中午(midday),晚上(night);
-
抽离动作
总共有三个动作,分别是吃早餐(breakfast)、吃午餐(lunch)、吃晚餐(dinner);
且我们知道,只有在早上才能吃早餐,中午才能吃晚餐,晚上才能吃晚餐。
三、模型实现
我们将状态和动作分别进行设计,在设计初期互不干扰,直到后续业务流程才需要做功能职责区分。
- 首先我们定义一个状态枚举类:
public enum StatusEnum {
/**
* 0:早上
*/
MORNING(0, "早上"),
/**
* 1:中午
*/
LUNCH(1, "中午"),
/**
* 2:晚上
*/
DINNER(2, "晚上");
private Integer currentStatus;
private String info;
StatusEnum(Integer currentStatus, String info) {
this.currentStatus = currentStatus;
this.info = info;
}
public Integer getCurrentStatus() {
return currentStatus;
}
public void setCurrentStatus(Integer currentStatus) {
this.currentStatus = currentStatus;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public static StatusEnum getStatusEnumByCurrentStatus(Integer currentStatus){
for(StatusEnum statusEnum : StatusEnum.values()){
if(statusEnum.getCurrentStatus().equals(currentStatus)){
return statusEnum;
}
}
return null;
}
}
- 然后我们需要定义一个动作抽象类、动作接口,抽象类和接口的方法定义需保持一致:
public abstract class AbstractAction {
// 早餐动作
public abstract boolean breakfast();
// 午餐动作
public abstract boolean lunch();
// 晚餐动作
public abstract boolean dinner();
}
public interface IStatusHandler {
// 早餐动作
boolean breakfast();
// 午餐动作
boolean lunch();
// 晚餐动作
boolean dinner();
}
-
继承动作抽象类定义三个状态动作类
@Component public class MorningAction extends AbstractAction { @Override public boolean breakfast(){ // 现在是早上,我能吃早餐 return true; } @Override public boolean lunch(){ // 现在是早上,我不能吃午餐 return false; } @Override public boolean dinner(){ // 现在是早上,我不能吃晚餐 return false; } }
@Component public class MiddayAction extends AbstractAction { @Override public boolean breakfast(){ // 现在是中午,我不能吃早餐 return false; } @Override public boolean lunch(){ // 现在是中午,我能吃午餐 return true; } @Override public boolean dinner(){ // 现在是中午,我不能吃晚餐 return false; } }
@Component public class NightAction extends AbstractAction { @Override public boolean breakfast(){ // 现在是晚上,我不能吃早餐 return false; } @Override public boolean lunch(){ // 现在是晚上,我不能吃午餐 return false; } @Override public boolean dinner(){ // 现在是晚上,我能吃晚餐 return true; } }
-
装配状态动作管理容器,初始化三个状态动作类后可通过枚举类进行获取
public class StatusActionConfig { @Resource private MorningAction morningAction; @Resource private MiddayAction middayAction; @Resource private NightAction nightAction; protected Map<Enum<StatusEnum>, AbstractAction> statusGroup = new ConcurrentHashMap<>(); @PostConstruct public void init() { statusGroup.put(StatusEnum.MORNING, morningAction); statusGroup.put(StatusEnum.LUNCH, middayAction); statusGroup.put(StatusEnum.DINNER, nightAction); } }
-
统一提供服务
@Service public class StatusActionHandleImpl extends StatusActionConfig implements IStatusHandler { @Override public Result breakfast(Enum<StatusEnum> currentStatus) { // 根据currentStatus当前状态自动从容器中判断是否能吃早餐 return statusGroup.get(currentStatus).breakfast(); } @Override public Result lunch(Enum<StatusEnum> currentStatus) { // 根据currentStatus当前状态自动从容器中判断是否能吃午餐 return statusGroup.get(currentStatus).lunch(); } @Override public Result dinner(Enum<StatusEnum> currentStatus) { // 根据currentStatus当前状态自动从容器中判断是否能吃晚餐 return statusGroup.get(currentStatus).dinner(); } }
-
调用实例
@Autowired IStatusHandler statusHandler; // 假设我传入 currentStatus = 0表示早上 @Test public void test(Integer currentStatus){ // 自动从装配容器中取出MorningAction,并通过MorningAction的各个动作去执行 statusHandler.breakfast(StatusEnum.getStatusEnumByCurrentStatus(currentStatus)); // true statusHandler.lunch(StatusEnum.getStatusEnumByCurrentStatus(currentStatus)); // false statusHandler.dinner(StatusEnum.getStatusEnumByCurrentStatus(currentStatus)); // false }
至此,我们一个简单的状态模式就完成了。最主要的是我们需要剥离业务中的状态和动作,最后再进行装配,这种巧妙的设计模式能够让我们的职责更加分明,扩展更加方便。
加入我们需要加入一个新的动作,吃点心。那么我们在IStatusHandler、AbstractAction、StatusEnum中赋予吃点心的动作,并在具体实现中加入吃点心的方法。
比如我规定早上和中午都能做吃点心这个动作,那么我们在实现的时候只需要在早上和中午中的点心动作中return true(具体业务逻辑中可能需要做复杂操作,这里我们用true表示操作成功)表示可以吃点心,而晚上则return false表示不允许吃点心。
-
四、总结
好的设计模式是通过前辈们不断地总结提炼而形成的,我们需要去领悟设计模式,将他们的设计思想穿透到我们的业务代码中,不断地去追问理解为什么要用接口、为什么要用抽象类。
而实际上设计模式也能够大大的提高我们的编码效率以及功能职责拆分。
期望大家能从文中的举例可以受到一定的启发并完善自己工程中的实际状态动作设计。最后,希望大家晚上尽量别吃夜宵哦。
参考资料: