状态模式介绍
状态模式描述的是一个行为下的多种状态变更。例如网站的页面,在登录与没有登录的状态下我们看见的内容有时候是不一样的,如果不登录就不能展示某些页面。
活动审批状态转换场景
比如一个流程是由多个层级审批上线的
可以看到流程节点包括了各个状态到下一个状态的关联条件,比如审批通过才能到活动中,而不能从编辑中直接到活动中,而这些状态的转变就是要完成的场景。
很多程序员都开发过类似的业务,需要对活动或者一些配置进行审批才能对外发布,而审批的过程往往会随着系统的额重要程度的提高而设立多级控制,以保证一个活动可以安全上线,避免造成损失。当然,有时会用一些审批流的过程配置,也非常便于开发类似的流程,可以在配置中设定某个节点的审批人员。
场景模拟工程
基本活动信息
1 2 3 4 5 6 7 8 9 10
| @Data @AllArgsConstructor @NoArgsConstructor public class ActivityInfo { private String activityId; private String activityName; private Date beginTime; private Date endTime; private Enum<Status> status; }
|
活动枚举状态
1 2 3 4 5 6 7 8 9 10 11
| package com.bestrookie; public enum Status { Editing, Check, Pass, Refuse, Doing, Close, Open }
|
这里是活动的枚举:1、创建编辑 2、待审批 3、审批通过 4、审批拒绝 5、活动中 6、活动关闭 7、活动开启
活动服务接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package com.bestrookie; import com.bestrookie.pojo.ActivityInfo; import java.util.Date; import java.util.Map; import java.util.concurrent.ConcurrentHashMap;
public class ActivityService { private static Map<String, Enum<Status>> statusMap = new ConcurrentHashMap<>(); public static void init(String activityId,Enum<Status> status){ ActivityInfo activityInfo = new ActivityInfo(); activityInfo.setActivityId(activityId); activityInfo.setActivityName("早起学习打卡啦"); activityInfo.setStatus(status); activityInfo.setBeginTime(new Date()); activityInfo.setEndTime(new Date()); statusMap.put(activityId,status); }
public static ActivityInfo queryActivityInfo(String activityId){ ActivityInfo activityInfo = new ActivityInfo(); activityInfo.setActivityId(activityId); activityInfo.setActivityName("早起学习啊"); activityInfo.setStatus(statusMap.get(activityId)); activityInfo.setEndTime(new Date()); activityInfo.setBeginTime(new Date()); return activityInfo; }
public static Enum<Status> queryActivityStatus(String activityId){ return statusMap.get(activityId); }
public static synchronized void execStatus(String activityId,Enum<Status> beforeStatus,Enum<Status> afterStatus){ if (!beforeStatus.equals(statusMap.get(activityId))){ return; } statusMap.put(activityId,afterStatus); } }
|
这个静态类提供了活动的查询和状态变更接口 queryActivtiyInfo、queryActivtyStatus和execStatus。通过使用Map结构记录活动ID和状态变化信息,另外init方法初始活动数据。在实际的开发中,这类信息基本都是从数据库或Redis中获取。
违背设计模式实现
对于各种状态变更的情况,最直接的方式是使用if和else判断。每一个状态可以流转到下一个状态,使用嵌套的if语句实现。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| package com.bestrookie; import com.bestrookie.pojo.ActivityInfo; import com.bestrookie.util.Result;
public class ActivityExecStatusController {
public Result execStatus(String activityId,Enum<Status> beforeStatus, Enum<Status> afterStatus){ if (Status.Editing.equals(beforeStatus)){ if (Status.Check.equals(afterStatus) || Status.Close.equals(afterStatus)){ ActivityService.execStatus(activityId,beforeStatus,afterStatus); return new Result("0000","变更状态成功"); }else { return new Result("0001","变更状态失败"); } } if (Status.Pass.equals(beforeStatus)){ if (Status.Refuse.equals(afterStatus) || Status.Close.equals(afterStatus) || Status.Doing.equals(afterStatus)){ ActivityService.execStatus(activityId,beforeStatus,afterStatus); return new Result("0000","变更状态成功"); }else { return new Result("0001","变更状态失败"); } } if (Status.Refuse.equals(beforeStatus)){ if (Status.Editing.equals(afterStatus) || Status.Close.equals(afterStatus)){ ActivityService.execStatus(activityId,beforeStatus,afterStatus); return new Result("0000","变更状态成功"); }else { return new Result("0001","变更状态失败"); } } if (Status.Doing.equals(beforeStatus)){ if (Status.Close.equals(afterStatus)){ ActivityService.execStatus(activityId,beforeStatus,afterStatus); return new Result("0000","变更状态成功"); }else { return new Result("0001","变更状态失败"); } } if (Status.Close.equals(beforeStatus)){ if (Status.Open.equals(afterStatus)){ ActivityService.execStatus(activityId, beforeStatus, afterStatus); return new Result("0000","变更状态成功"); }else { return new Result("0001","变更状态失败"); } } if (Status.Open.equals(beforeStatus)){ if (Status.Close.equals(afterStatus)){ ActivityService.execStatus(activityId, beforeStatus, afterStatus); return new Result("0000","变更状态成功"); }else { return new Result("0001","变更状态失败"); } } return new Result("0002","非可处理的活动状态变更"); } }
|
从代码实现的结构即可,从上到下用了很多的if…else。对于不需要改动也不需要二次迭代的代码,这种面向过程式的开发方式还是可以使用的。但在真实场景中,基本不可能不迭代,随着状态和需求的变化,会越来越难以维护,后来的同时也不容易看懂,很容易将其他的流程填充进去。
状态模式重构代码
重构的重点往往是处理if…else,这离不开接口与抽象类,另外还需要重新改造代码结构。
其中,State是一个抽象类,定义了各种操作接口,包括提审、审批和拒审等。右侧的不同状态与前面的各种状态保持一致,是各种状态流程保持一致,是各种状态流程流转的实现操作。这里有一个关键点,对于每一种状态到下一个状态,都设置为在各个实现方法中控制,不需要if语句判断。最后,StateHandler对状态流程统一处理,里面提供Map结构的各项接口调用,避免使用if语句判断状态转变的流程。
定义抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| package com.bestrookie.design; import com.bestrookie.Status; import com.bestrookie.util.Result; public abstract class State {
public abstract Result arraignment(String activityId, Enum<Status> currentStatus);
public abstract Result checkPass(String activityId,Enum<Status> currenStatus);
public abstract Result checkRefuse(String activityId,Enum<Status> currenStatus);
public abstract Result checkRevoke(String activityId,Enum<Status> currentStatus);
public abstract Result close(String activityId,Enum<Status> currentStatus);
public abstract Result open(String activityId,Enum<Status> currentStatus);
public abstract Result doing(String activityId,Enum<Status> currentStatus); }
|
这个接口提供了各项状态流转服务的借口,例如活动提审、审批通过、审批拒绝和撤审撤销等七个方法。在这些方法中,所有的入参都是一样的,即activityId、currentStatus,只有各自的具体实现方式不同的。
部分状态流转实现
编辑状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| package com.bestrookie.design.event;
import com.bestrookie.ActivityService; import com.bestrookie.Status; import com.bestrookie.design.State; import com.bestrookie.util.Result;
public class EditingState extends State { @Override public Result arraignment(String activityId, Enum<Status> currentStatus) { ActivityService.execStatus(activityId,currentStatus,Status.Check); return new Result("0000","活动提审成功"); }
@Override public Result checkPass(String activityId, Enum<Status> currenStatus) { return new Result("0001","编辑中不可审批通过"); }
@Override public Result checkRefuse(String activityId, Enum<Status> currenStatus) { return new Result("0001","编辑中不可审批拒绝"); }
@Override public Result checkRevoke(String activityId, Enum<Status> currentStatus) { return new Result("0001","编辑中不可撤销审批"); }
@Override public Result close(String activityId, Enum<Status> currentStatus) { ActivityService.execStatus(activityId,currentStatus,Status.Close); return new Result("0000","活动关闭成功"); }
@Override public Result open(String activityId, Enum<Status> currentStatus) { return new Result("0001","非关闭活动不可开启"); }
@Override public Result doing(String activityId, Enum<Status> currentStatus) { return new Result("0001","编辑中活动不渴执行活动中变更"); } }
|
提审状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| package com.bestrookie.design.event;
import com.bestrookie.ActivityService; import com.bestrookie.Status; import com.bestrookie.design.State; import com.bestrookie.util.Result;
public class CheckState extends State { @Override public Result arraignment(String activityId, Enum<Status> currentStatus) { return new Result("0001","待审批状态不可重复提审"); }
@Override public Result checkPass(String activityId, Enum<Status> currenStatus) { ActivityService.execStatus(activityId,currenStatus,Status.Pass); return new Result("0000","活动审批完成"); }
@Override public Result checkRefuse(String activityId, Enum<Status> currenStatus) { ActivityService.execStatus(activityId,currenStatus,Status.Refuse); return new Result("0000","活动拒绝完成"); }
@Override public Result checkRevoke(String activityId, Enum<Status> currentStatus) { ActivityService.execStatus(activityId,currentStatus,Status.Editing); return new Result("0000","活动审批撤销回到编辑中"); }
@Override public Result close(String activityId, Enum<Status> currentStatus) { ActivityService.execStatus(activityId,currentStatus,Status.Editing); return new Result("0000","活动审批关闭完成"); }
@Override public Result open(String activityId, Enum<Status> currentStatus) { return new Result("0001","非关闭活动不可开启"); }
@Override public Result doing(String activityId, Enum<Status> currentStatus) { return new Result("0001","待审批活动不可执行活动中变更"); } }
|
这里提供两个具体实现类的内容–编辑状态和提审状态。
状态处理服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| package com.bestrookie.design; import com.bestrookie.Status; import com.bestrookie.design.event.*; import com.bestrookie.util.Result; import java.util.Map; import java.util.concurrent.ConcurrentHashMap;
public class StateHandler { private Map<Enum<Status>,State> stateMap = new ConcurrentHashMap<>(); public StateHandler(){ stateMap.put(Status.Check,new CheckState()); stateMap.put(Status.Close,new CloseState()); stateMap.put(Status.Doing,new DoingState()); stateMap.put(Status.Editing,new EditingState()); stateMap.put(Status.Open,new OpenState()); stateMap.put(Status.Pass,new PassState()); stateMap.put(Status.Refuse,new RefuseState()); } public Result arraignment(String activityId, Enum<Status> currentStatus) { return stateMap.get(currentStatus).arraignment(activityId, currentStatus); }
public Result checkPass(String activityId, Enum<Status> currentStatus) { return stateMap.get(currentStatus).checkPass(activityId, currentStatus); }
public Result checkRefuse(String activityId, Enum<Status> currentStatus) { return stateMap.get(currentStatus).checkRefuse(activityId, currentStatus); }
public Result checkRevoke(String activityId, Enum<Status> currentStatus) { return stateMap.get(currentStatus).checkRevoke(activityId, currentStatus); }
public Result close(String activityId, Enum<Status> currentStatus) { return stateMap.get(currentStatus).close(activityId, currentStatus); }
public Result open(String activityId, Enum<Status> currentStatus) { return stateMap.get(currentStatus).open(activityId, currentStatus); }
public Result doing(String activityId, Enum<Status> currentStatus) { return stateMap.get(currentStatus).doing(activityId, currentStatus); } }
|
这是对状态服务的统一控制中心,可以看到在构造函数中提供了所有状态和实现的具体关联,并放到了Map数据结构中。同时,提供了不同名称的接口操作类,让外部调用方可以更加容易地使用此项目功能接口。
TestApi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.bestrookie.design; import com.alibaba.fastjson.JSON; import com.bestrookie.ActivityService; import com.bestrookie.Status; import com.bestrookie.util.Result;
public class TestApi { public static void main(String[] args) { String activityId = "100001"; ActivityService.init(activityId, Status.Editing); StateHandler stateHandler = new StateHandler(); Result result = stateHandler.arraignment(activityId,Status.Editing); System.out.println(JSON.toJSON(result)); Result result1 = stateHandler.open(activityId,Status.Editing); System.out.println(JSON.toJSON(result1)); System.out.println(JSON.toJSON(ActivityService.queryActivityInfo(activityId).getStatus())); } }
|
总结
从以上两种方式对一个需求的实现对比可以看到,在使用设计模式处理后,已经没有了if…else,代码的结构也更加清晰,易于扩展。在实现结构的编码方式上,可以看到不再是面向过程的编程,而是面向对象的编程。并且这种设计模式满足了单一职责和开闭原则,当只有满足这种结构时,才会发现代码的扩展是容易的,也就是增加或修改功能不会影响整体。如果状态和各项流传较多,就会产生较多的实现类。因此,可能会给代码的实现增加时间成本,因为如果遇到这种场景可以按需评估投入回报率,主要在于是否会经常修改,是否可以做成组件化,抽离业务功能与非业务功能。