• 【设计模式】状态模式


    一、前言

    状态模式,就是用类表示状态,好处是能通过切换类来方便地改变对象的状态,如果需要新加状态时也不用修改之前的代码。

    二、介绍

    假设现在有一个金库,金库和警报中心相连,金库里有警铃和通话用的电话,还有个时钟监视着现在的时间,白天或晚上使用警铃或打电话的表现都不一样。

    不使用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 + "
    ");
        }
    
        @Override
        public void recordLog(String msg) {
            textScreen.append("record...." + msg + "
    ");
        }
    
        @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逻辑判断后面难以维护,代码如下:

    1. /** 
    2. * @author Ship 
    3. * @date 2020-01-08 17:10 任务结果service 
    4. */ 
    5. @Slf4j 
    6. @Service 
    7. public class SupplierTaskResultServiceImpl implements SupplierTaskResultService
    8.  
    9.  
    10.  
    11. @Override 
    12. public TaskResultDTO findTaskResult(Integer taskId)
    13. TaskResultDTO taskResultDTO = new TaskResultDTO(); 
    14. SupplierTaskDTO supplierTaskDTO = supplierTaskMapper.queryById(taskId); 
    15. Byte source = supplierTaskDTO.getSource(); 
    16. Byte type = supplierTaskDTO.getType(); 
    17. if (TaskSourceType.STANDARD.getCode().equals(source)) { 
    18. // 标准 
    19. if (type.equals(TaskType.ACTIVE_VISIT.getValue())) { 
    20. ... 
    21. if (type.equals(TaskType.NEW_CUSTOMER_INFO.getValue()) || type.equals(TaskType.UPDATE_CUSTOMER_INFO.getValue())) { 
    22. ... 
    23.  
    24. if (type.equals(TaskType.COLLECT_REQUIREMENT_ORDER.getValue())) { 
    25. ... 
    26. } else if (TaskSourceType.SYS_AUTO.getCode().equals(source)) { 
    27. // 自动 
    28. ... 
    29.  
    30. } else if (TaskSourceType.DECISION.getCode().equals(source)) { 
    31. // 决策 
    32. if (TaskType.ADJUST_PRICE.getValue().equals(type)) { 
    33. ... 
    34. if (TaskType.COLLECT_INFO.getValue().equals(type)) { 
    35. ... 
    36. if (TaskType.SEND_MESSAGE.getValue().equals(type)) { 
    37. ... 
    38. } else
    39. throw new BusinessException("任务触发类型有误"); 
    40. if (Objects.isNull(taskResultDTO) || null == taskResultDTO.getType()) { 
    41. taskResultDTO = new TaskResultDTO(); 
    42. taskResultDTO.setType(type); 
    43. return taskResultDTO; 
    44.  
    45. @Override 
    46. public int addTaskResult(TaskResultDTO taskResultDTO)
    47. log.info("taskResultDTO--->{}", taskResultDTO); 
    48. if (Objects.isNull(taskResultDTO.getTaskSourceType())) { 
    49. throw new BusinessException("添加任务结果失败:任务触发类型不能为空"); 
    50. if (Objects.isNull(taskResultDTO.getTaskType()) && !TaskSourceType.SYS_AUTO.equals(taskResultDTO.getTaskSourceType())) { 
    51. throw new BusinessException("添加任务结果失败:任务类型不能为空"); 
    52.  
    53. if (Objects.isNull(taskResultDTO.getTaskId())){ 
    54. throw new BusinessException("添加任务结果失败:任务ID不能为空"); 
    55. taskResultDTO.setRecordTime(LocalDateTime.now()); 
    56. Byte type = taskResultDTO.getTaskType().getValue(); 
    57. Byte source = taskResultDTO.getTaskSourceType().getCode(); 
    58. if (TaskSourceType.STANDARD.getCode().equals(source)) { 
    59. // 标准 
    60. if (type.equals(TaskType.ACTIVE_VISIT.getValue())) { 
    61. ... 
    62.  
    63. if (type.equals(TaskType.NEW_CUSTOMER_INFO.getValue()) || type.equals(TaskType.UPDATE_CUSTOMER_INFO.getValue())) { 
    64. //建档 
    65. ... 
    66.  
    67. if (type.equals(TaskType.COLLECT_REQUIREMENT_ORDER.getValue())) { 
    68. ... 
    69.  
    70. } else if (TaskSourceType.SYS_AUTO.getCode().equals(source)) { 
    71. // 自动 
    72. ... 
    73.  
    74. } else if (TaskSourceType.DECISION.getCode().equals(source)) { 
    75. // 决策 
    76. if (TaskType.ADJUST_PRICE.getValue().equals(type)) { 
    77.  
    78. ... 
    79.  
    80. if (TaskType.COLLECT_INFO.getValue().equals(type)) { 
    81. ... 
    82.  
    83. if (TaskType.SEND_MESSAGE.getValue().equals(type)) { 
    84.  
    85. ... 
    86. } else
    87. throw new BusinessException("任务触发类型有误"); 
    88. return 0
    89.  
    90.  

    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;
        }
    
    }
    
    

    四、总结

    总之,状态模式的优缺点有这几个,要根据自己的实际场景决定是否使用。
    优点:

    1. 避免了大量的if/else判断,每个类单独维护自己的业务代码,职责更清晰。
    2. 如果需要增加新的状态,只需要重新写个类实现Sate接口即可,不需要修改之前的代码。
    3. 对调用方隐藏了自己的内部实现

    缺点:

    1. 会生成大量的类文件
    2. 如果Sate接口增加了一个方法,那么不管其他ConcreteState类需不需要这个方法都必须强制实现,而且类越多修改起来就越麻烦。
  • 相关阅读:
    Clickhouse SQL语法
    Clickhouse副本及分片
    Clickhouse入门及实践
    Flink CDC 与Hudi整合
    分布式相关理论及算法
    ClickHouse查询优化
    ios之OC与C、OC与c++互相调用OC与C++的互相调用
    前端 base64加密 及 md5加密
    CSS实现文字对齐效果总结
    十分钟学会Centos7下无图形界面安装 Oracle11g
  • 原文地址:https://www.cnblogs.com/2YSP/p/12772359.html
Copyright © 2020-2023  润新知