【设计模式】状态模式
一、前言
状态模式,就是用类表示状态,好处是能通过切换类来方便地改变对象的状态,如果需要新加状态时也不用修改之前的代码。
二、介绍
假设现在有一个金库,金库和警报中心相连,金库里有警铃和通话用的电话,还有个时钟监视着现在的时间,白天或晚上使用警铃或打电话的表现都不一样。
不使用State模式的伪代码如下:
警报系统的类{ 使用金库时被调用的方法(){ if(白天){ 向警报中心报告使用记录 }else if(晚上){ 向警报中心报告紧急事态 } } 警铃响起时被调用的方法(){ 向警报中心报告紧急事态 } 正常通话时被调用的方法(){ if(白天){ 呼叫警报中心 }else if(晚上){ 呼叫警报中心的留言电话 } } }
使用State模式的伪代码:
表示白天的状态的类{ 使用金库时被调用的方法(){ 向警报中心报告使用记录 } 警铃响起时被调用的方法(){ 向警报中心报告紧急事态 } 正常通话时被调用的方法(){ 呼叫警报中心 } } 表示晚上的状态的类{ 使用金库时被调用的方法(){ 向警报中心报告紧急事态 } 警铃响起时被调用的方法(){ 向警报中心报告紧急事态 } 正常通话时被调用的方法(){ 呼叫警报中心的留言电话 } }
在使用了State模式后,我们用类表示白天还是晚上,在类的各个方法中就不需要if语句判断现在是白天还是晚上了。

State接口
public interface State { // 设置时间 void doClock(Context context,int hour); // 使用金库 void doUse(Context context); /** * 按下警铃 * @param context */ void doAlarm(Context context); /** * 正常通话 * @param context */ void doPhone(Context context); }
表示白天的类
public class DayState implements State { private static DayState singleton = new DayState(); private DayState(){ } public static State getInstance(){ return singleton; } @Override public void doClock(Context context, int hour) { if (hour < 9 || 17 <= hour){ context.changeState(NightState.getInstance()); } } @Override public void doUse(Context context) { context.recordLog("使用金库(白天)"); } @Override public void doAlarm(Context context) { context.callSecurityCenter("按下警铃(白天)"); } @Override public void doPhone(Context context) { context.callSecurityCenter("正常通话(白天)"); } public String toString(){ return "[白天]"; } }
表示晚上的类
public class NightState implements State { private static NightState singleton = new NightState(); private NightState() { } public static NightState getInstance() { return singleton; } @Override public void doClock(Context context, int hour) { if (9 <= hour && hour < 17) { context.changeState(DayState.getInstance()); } } @Override public void doUse(Context context) { context.callSecurityCenter("紧急:晚上使用金库!"); } @Override public void doAlarm(Context context) { context.callSecurityCenter("按下警铃(晚上)"); } @Override public void doPhone(Context context) { context.recordLog("晚上的通话录音"); } public String toString() { return "[晚上]"; } }
上下文
public interface Context { /** * 设置时间 * @param hour */ void setClock(int hour); /** * 改变状态 * @param state */ void changeState(State state); /** * 联系警报中心 * @param msg */ void callSecurityCenter(String msg); /** * 在警报中心留下记录 * @param msg */ void recordLog(String msg); }
SateFrame持有State
public class SateFrame extends Frame implements ActionListener, Context { /** * 显示当前时间 */ private TextField textClock = new TextField(60); /** * 显示警报中心的记录 */ private TextArea textScreen = new TextArea(10, 60); private Button buttonUse = new Button("使用金库"); private Button buttonAlarm = new Button("按下警铃"); private Button buttonPhone = new Button("正常通话"); private Button buttonExit = new Button("结束"); /** * 当前的状态 */ private State state = DayState.getInstance(); public SateFrame(String title) { super(title); setBackground(Color.lightGray); setLayout(new BorderLayout()); add(textClock, BorderLayout.NORTH); textClock.setEditable(false); add(textScreen, BorderLayout.CENTER); textScreen.setEditable(false); // 为界面添加按钮 Panel panel = new Panel(); panel.add(buttonUse); panel.add(buttonAlarm); panel.add(buttonPhone); panel.add(buttonExit); add(panel, BorderLayout.SOUTH); pack(); show(); buttonUse.addActionListener(this); buttonAlarm.addActionListener(this); buttonPhone.addActionListener(this); buttonExit.addActionListener(this); } @Override public void setClock(int hour) { String clockString = "现在时间是"; if (hour < 10) { clockString += "0" + hour + ":00"; } else { clockString += hour + ":00"; } System.out.println(clockString); textClock.setText(clockString); state.doClock(this, hour); } @Override public void changeState(State state) { System.out.println("从" + this.state + "状态变为了" + state + "状态。"); this.state = state; } @Override public void callSecurityCenter(String msg) { textScreen.append("call! " + msg + "\n"); } @Override public void recordLog(String msg) { textScreen.append("record...." + msg + "\n"); } @Override public void actionPerformed(ActionEvent e) { System.out.println(e.toString()); if (e.getSource() == buttonUse) { state.doUse(this); } else if (e.getSource() == buttonAlarm) { state.doAlarm(this); } else if (e.getSource() == buttonPhone) { state.doPhone(this); } else if (e.getSource() == buttonExit) { System.exit(0); } else { System.out.println("?"); } } }
测试类Main
public class Main { public static void main(String[] args) { SateFrame frame = new SateFrame("State Sample"); while (true) { for (int hour = 0; hour < 24; hour++) { frame.setClock(hour); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
状态模式通常有如下几种角色:
-
State(状态)
Sate角色表示状态,定义了根据不同状态进行不同处理的API,对应示例中的State接口。 -
ConcreteState(具体状态)
表示各个具体状态,对应示例中的DayState和NightState -
Context(状况、前后关系、上下文)
Context角色持有表示当前状态的ConcreteState角色。

三、实战
再看看我在项目中是如何使用State模式的。
3.1需求背景
公司的数字化营销系统为了调动销售的积极性,面向过程而不是结果,设计了一套任务系统,当某些前置条件(如客户提交了售后)满足时系统会自动给销售发送任务。
由于任务的类型很多有十几种而且每种任务的结果表单都不同,当时一番讨论后采用了将任务结果表拆分成多个表的方式,比如A类型任务的任务结果表是t_a,B类型的
任务的任务结果表是t_b。。。。
3.2痛点
随着任务种类越来越多,任务结果Service类的添加任务结果和查询任务结果方法中就充斥着大量的if/else逻辑判断后面难以维护,代码如下:
- /**
- * @author Ship
- * @date 2020-01-08 17:10 任务结果service
- */
- 4j
- public class SupplierTaskResultServiceImpl implements SupplierTaskResultService {
- public TaskResultDTO findTaskResult(Integer taskId) {
- TaskResultDTO taskResultDTO = new TaskResultDTO();
- SupplierTaskDTO supplierTaskDTO = supplierTaskMapper.queryById(taskId);
- Byte source = supplierTaskDTO.getSource();
- Byte type = supplierTaskDTO.getType();
- if (TaskSourceType.STANDARD.getCode().equals(source)) {
- // 标准
- if (type.equals(TaskType.ACTIVE_VISIT.getValue())) {
- ...
- }
- if (type.equals(TaskType.NEW_CUSTOMER_INFO.getValue()) || type.equals(TaskType.UPDATE_CUSTOMER_INFO.getValue())) {
- ...
- }
- if (type.equals(TaskType.COLLECT_REQUIREMENT_ORDER.getValue())) {
- ...
- }
- } else if (TaskSourceType.SYS_AUTO.getCode().equals(source)) {
- // 自动
- ...
- } else if (TaskSourceType.DECISION.getCode().equals(source)) {
- // 决策
- if (TaskType.ADJUST_PRICE.getValue().equals(type)) {
- ...
- }
- if (TaskType.COLLECT_INFO.getValue().equals(type)) {
- ...
- }
- if (TaskType.SEND_MESSAGE.getValue().equals(type)) {
- ...
- }
- } else {
- throw new BusinessException("任务触发类型有误");
- }
- if (Objects.isNull(taskResultDTO) || null == taskResultDTO.getType()) {
- taskResultDTO = new TaskResultDTO();
- taskResultDTO.setType(type);
- }
- return taskResultDTO;
- }
- public int addTaskResult(TaskResultDTO taskResultDTO) {
- log.info("taskResultDTO--->{}", taskResultDTO);
- if (Objects.isNull(taskResultDTO.getTaskSourceType())) {
- throw new BusinessException("添加任务结果失败:任务触发类型不能为空");
- }
- if (Objects.isNull(taskResultDTO.getTaskType()) && !TaskSourceType.SYS_AUTO.equals(taskResultDTO.getTaskSourceType())) {
- throw new BusinessException("添加任务结果失败:任务类型不能为空");
- }
- if (Objects.isNull(taskResultDTO.getTaskId())){
- throw new BusinessException("添加任务结果失败:任务ID不能为空");
- }
- taskResultDTO.setRecordTime(LocalDateTime.now());
- Byte type = taskResultDTO.getTaskType().getValue();
- Byte source = taskResultDTO.getTaskSourceType().getCode();
- if (TaskSourceType.STANDARD.getCode().equals(source)) {
- // 标准
- if (type.equals(TaskType.ACTIVE_VISIT.getValue())) {
- ...
- }
- if (type.equals(TaskType.NEW_CUSTOMER_INFO.getValue()) || type.equals(TaskType.UPDATE_CUSTOMER_INFO.getValue())) {
- //建档
- ...
- }
- if (type.equals(TaskType.COLLECT_REQUIREMENT_ORDER.getValue())) {
- ...
- }
- } else if (TaskSourceType.SYS_AUTO.getCode().equals(source)) {
- // 自动
- ...
- } else if (TaskSourceType.DECISION.getCode().equals(source)) {
- // 决策
- if (TaskType.ADJUST_PRICE.getValue().equals(type)) {
- ...
- }
- if (TaskType.COLLECT_INFO.getValue().equals(type)) {
- ...
- }
- if (TaskType.SEND_MESSAGE.getValue().equals(type)) {
- ...
- }
- } else {
- throw new BusinessException("任务触发类型有误");
- }
- return 0;
- }
- }
3.3使用State模式后
TaskResultProcessor相当于State接口
/** * Created by 2YSP on 2020/2/12. */ public interface TaskResultProcessor { /** * 插入任务结果 * * @param taskResultDTO * @return */ Integer doAddTaskResult(TaskResultDTO taskResultDTO); /** * 查询任务结果 * * @param taskId * @param taskType * @return */ TaskResultDTO doFindTaskResult(Integer taskId, TaskType taskType); /** * 更新业务员位置信息 * * @param taskResultDTO * @param resultId */ void updateSellerPosition(TaskResultDTO taskResultDTO, Integer resultId); /** * 支持的任务类型 * * @return */ List<TaskType> supportTaskType(); }

每种任务的结果处理器实现TaskResultProcessor接口,如AdjustTaskResultProcessor
@Slf4j @Service @RequiredArgsConstructor public class AdjustTaskResultProcessor implements TaskResultProcessor { private final AdjustTaskResultMapper adjustTaskResultMapper; @Override public Integer doAddTaskResult(TaskResultDTO taskResultDTO) { ... } @Override public TaskResultDTO doFindTaskResult(Integer taskId, TaskType taskType) { ... } @Override public void updateSellerPosition(TaskResultDTO taskResultDTO, Integer resultId) { ... } @Override public List<TaskType> supportTaskType() { return Lists.newArrayList(TaskType.ADJUST_PRICE); } }
TaskResultProcessorHolder持有所有的任务结果处理器,利用构造方法注入在项目启动时将Bean注入到PROCESSOR_MAP中。
@Component public class TaskResultProcessorHolder { private static final Map<TaskType, TaskResultProcessor> PROCESSOR_MAP = new ConcurrentHashMap<>(); @Autowired public TaskResultProcessorHolder(List<TaskResultProcessor> taskResultProcessors) { taskResultProcessors.forEach(taskResultProcessor -> { List<TaskType> taskTypes = taskResultProcessor.supportTaskType(); taskTypes.forEach(t -> PROCESSOR_MAP.put(t, taskResultProcessor)); }); } public Optional<TaskResultProcessor> getProcessor(TaskType taskType) { return Optional.ofNullable(PROCESSOR_MAP.get(taskType)); } }
SupplierTaskResultServiceImpl作为调用方,代码就简化了很多。
/** * @author Ship * @date 2020-01-08 17:10 任务结果service */ @Slf4j @Service public class SupplierTaskResultServiceImpl implements SupplierTaskResultService { @Autowired private SupplierTaskMapper supplierTaskMapper; @Autowired private MQSender mqSender; @Autowired private TaskResultProcessorHolder processorHolder; @Override public TaskResultDTO findTaskResult(Integer taskId) { SupplierTaskDTO supplierTaskDTO = supplierTaskMapper.queryById(taskId); Byte type; if (TaskSourceType.SYS_AUTO.getCode().equals(supplierTaskDTO.getSource())) { type = TaskType.EARLY_WARNING_PROCESSING.getValue(); } else { type = supplierTaskDTO.getType(); } // 直接调用任务结果处理器的方法即可 TaskResultProcessor processor = getTaskResultProcessor(type); return processor.doFindTaskResult(taskId, TaskType.getTaskType(type)); } @Override public int addTaskResult(TaskResultDTO taskResultDTO) { log.info("taskResultDTO--->{}", JSON.toJSON(taskResultDTO)); // 参数校验。。。 taskResultDTO.setRecordTime(LocalDateTime.now()); // 自动任务时,type不需要 Byte type; if (TaskSourceType.SYS_AUTO.equals(taskResultDTO.getTaskSourceType())) { type = TaskType.EARLY_WARNING_PROCESSING.getValue(); } else { type = taskResultDTO.getTaskType().getValue(); } // 直接调用任务结果处理器的方法即可 TaskResultProcessor processor = getTaskResultProcessor(type); Integer resultId = processor.doAddTaskResult(taskResultDTO); mqSender.updateSellerPosition(getSellerPositionDTO(taskResultDTO, resultId)); return resultId; } @Override public void updateSellerPosition(SellerPositionDTO positionDTO) { StopWatch stopWatch = new StopWatch(); stopWatch.start("异步更新任务结果中的业务员位置信息"); TaskResultDTO taskResultDTO = new TaskResultDTO(); // 根据经纬度解析地址... Byte type; if (TaskSourceType.SYS_AUTO.getCode().equals(positionDTO.getTaskSource())) { type = TaskType.EARLY_WARNING_PROCESSING.getValue(); } else { type = positionDTO.getTaskType(); } TaskResultProcessor processor = getTaskResultProcessor(type); processor.updateSellerPosition(taskResultDTO, positionDTO.getResultId()); stopWatch.stop(); log.info(stopWatch.prettyPrint()); } /** * 根据任务类型获取对应的结果处理器 * * @param type * @return */ private TaskResultProcessor getTaskResultProcessor(Byte type) { if (Objects.isNull(type)) { throw new BusinessException("任务类型不能为空"); } TaskType taskType = TaskType.getTaskType(type); TaskResultProcessor processor = processorHolder.getProcessor(taskType) .orElseThrow(() -> new BusinessException("任务触发类型有误")); return processor; } }
四、总结
总之,状态模式的优缺点有这几个,要根据自己的实际场景决定是否使用。
优点:
- 避免了大量的if/else判断,每个类单独维护自己的业务代码,职责更清晰。
- 如果需要增加新的状态,只需要重新写个类实现Sate接口即可,不需要修改之前的代码。
- 对调用方隐藏了自己的内部实现
缺点:
- 会生成大量的类文件
- 如果Sate接口增加了一个方法,那么不管其他ConcreteState类需不需要这个方法都必须强制实现,而且类越多修改起来就越麻烦。
本文作者:烟味i
本文链接:https://www.cnblogs.com/2YSP/p/12772359.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步