• 你还不知道责任链模式的使用场景吗?


    概述#

    在代码中我们经常会有if…else…判断,一个条件不满足就进行下一个判断,这种就类似于责任链模式,只不过责任链模式是通过对象来过滤。

    场景#

    在物联网行业中,一个设备会以一定的频率向服务器推送数据,方便服务器对机器进行一个数据采集和监控,这个数据的类型是多种多样的。例如娃娃机来说:会有设备状态的数据、设备定位的数据、设备报警的数据等等各种数据。每一种类型的数据都由很多个字段组成,例如设备状态数据包含:当前时间、机器号、机器状态(上线、下线、离线),一般都是以二进制的形式进行传输,为了方便就假设设备以JSON的格式上报过来,我接收到数据再进行一个相应的处理。

    image-20220421214953234

    普通的代码实现#

    首先能想到的就是利用if…else…,如果是设备报警的数据我就使用设备报警处理器处理,超简单的,开始编码~

    1、实体类

    DeviceAlarm类

    package com.ylc.model;
    
    import lombok.Data;
    
    /**
     * 设备状态实体类
     * @author yanglingcong
     * @date 2022/4/20 21:08
     */
    @Data
    public class DeviceStatus {
    
    
        /**
         * 更新时间
         */
        private  long updateTime;
    
        /**
         * 状态
         * 0 未准备
         * 1 准备
         * 2 正常运行
         * 3 异常
         */
        private Integer state;
    
        /**
         * 数据类型
         */
        private String type;
    
    }
    
    

    DeviceGps类

    /**
     * 设备GPS实体类
     *
     * @author yanglingcong
     * @date 2022/4/20 21:08
     */
    @Data
    public class DeviceGps {
    
        /**
         * 经度
         */
        private Float longitude;
    
        /**
         * 纬度
         */
        private Float latitude;
    
    
        /**
         * 水平分量精度因子:
         */
        private Float hdop;
    
    }
    

    DeviceAlarm类

    package com.ylc.model;
    
    import lombok.Data;
    
    /**
     * 设备报警实体类
     *
     * @author yanglingcong
     * @date 2022/4/20 21:08
     */
    @Data
    public class DeviceAlarm {
    
        /**
         * 报警消息
         */
        private String alarmMsg;
    
        /**
         * 报警状态
         */
        private Integer alarmStatus;
    }
    
    

    2、消息的枚举类型

    package com.ylc.model;
    
    import lombok.Getter;
    
    /**
     * 设备消息枚举类型
     * @author yanglingcong
     * @date 2022/4/20 21:08
     */
    @Getter
    public enum eventEnum {
    
        STATUS("10001"),
    
        ALARM("10002"),
    
        GPS("10003");
    
        private String code;
    
        eventEnum(String code){
            this.code=code;
        }
    }
    
    

    3、事件接口

    /**
     * 处理器接口
     * @author yanglingcong
     * @date 2022/4/19 22:59
     */
    public interface AbstractHandler {
    
        String getEventType();
    
        void handle(JSONObject jsonObject);
    
    }
    

    3、事件处理

    DeviceAlarmEvent

    /**
     * 设备报警事件
     * @author yanglingcong
     * @date 2022/4/19 22:59
     */
    @Slf4j
    @Component
    public class DeviceAlarmEvent   implements  AbstractHandler{
    
        @Override
        public String getEventType() {
            return eventEnum.ALARM.getCode();
        }
    
        @Override
        public void handle(JSONObject jsonObject) {
            DeviceAlarm deviceAlarm = jsonObject.toJavaObject(DeviceAlarm.class);
            log.info("设备报警事件被处理");
            //业务处理.....
        }
    }
    
    

    DeviceGpsEvent

    /**
     * 设备定位事件
     * @author yanglingcong
     * @date 2022/4/19 22:59
     */
    @Component
    @Slf4j
    public class DeviceGpsEvent implements AbstractHandler{
    
        @Override
        public String getEventType() {
            return eventEnum.GPS.getCode();
        }
    
        @Override
        public void handle(JSONObject jsonObject) {
            DeviceGps deviceGps = jsonObject.toJavaObject(DeviceGps.class);
            //业务处理.....
            log.info("设备定位事件被处理");
        }
    }
    
    

    DeviceStatusEvent

    /**
     * 设备状态事件
     * @author yanglingcong
     * @date 2022/4/19 22:59
     */
    @Slf4j
    @Component
    public class DeviceStatusEvent implements  AbstractHandler{
    
        @Override
        public String getEventType() {
            return eventEnum.STATUS.getCode();
        }
    
        @Override
        public   void  handle(JSONObject jsonObject){
            DeviceStatus deviceStatus = jsonObject.toJavaObject(DeviceStatus.class);
            //业务处理.....
            log.info("设备状态事件被处理");
        }
    }
    
    

    4、消息分发中心

    package com.ylc.handle;
    
    import com.alibaba.fastjson.JSONObject;
    import com.ylc.model.eventEnum;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;
    
    /**
     * 数据事件处理类
     * @author yanglingcong
     */
    @Slf4j
    @Component
    public class PushEvent {
    
        /**
         * 数据分发到对应的事件处理
         */
        public void dispatch(JSONObject jsonObject){
            String code = (String) jsonObject.get("type");
            //如果是设备状态数据
            if(code.equals(eventEnum.STATUS.getCode())){
                log.info("开始处理设备状态数据");
                DeviceStatusEvent statusEvent=new DeviceStatusEvent();
                statusEvent.handle(jsonObject);
            }
            //如果是设备定位数据
            else if(code.equals(eventEnum.GPS.getCode())){
                log.info("开始处理设备定位数据");
                DeviceGpsEvent deviceGpsEvent=new DeviceGpsEvent();
                deviceGpsEvent.handle(jsonObject);
            }
            //如果是设备报警数据
            else if(code.equals(eventEnum.ALARM.getCode())){
                log.info("开始处理设备定位数据");
                DeviceStatusEvent statusEvent=new DeviceStatusEvent();
                statusEvent.handle(jsonObject);
            }
        }
    
    }
    
    

    6、测试

    @Slf4j
    public class MessageHandleTest {
    
        @Test
        public void  testDeviceStatus(){
            DeviceStatus deviceStatus=new DeviceStatus();
            deviceStatus.setType(eventEnum.STATUS.getCode());
            deviceStatus.setUpdateTime(1653532367);
            deviceStatus.setState(1);
            JSONObject jsonObject= JSON.parseObject(JSONObject.toJSONString(deviceStatus));
            PushEvent pushEvent=new PushEvent();
            log.info("开始分发消息:{}",deviceStatus.toString());
            pushEvent.dispatch(jsonObject);
        }
    
    }
    

    运行结果

    image-20220421221949165

    但是这样会有很多问题,如果还有其他类型的数据那么又要增加判断,这个条件判定的顺序也是写死的,非常不灵活,接下来用责任链模式进行优化

    责任链实现#

    1、实体类 略

    2、事件处理 略

    3、消息分发中心

    package com.ylc.handle;
    
    import com.alibaba.fastjson.JSONObject;
    import lombok.extern.slf4j.Slf4j;
    import lombok.var;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.annotation.DependsOn;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.Resource;
    import java.util.Collection;
    import java.util.Comparator;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    
    /**
     * 数据事件处理类
     * @author yanglingcong
     */
    @Slf4j
    @Component
    public class PushEvent implements ApplicationContextAware {
    
         /**
         * 实现类集合
         * */
        private Map<String, List<AbstractHandler>> routerMap;
    
        @Autowired
        ApplicationContext applicationContext;
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.routerMap =applicationContext.getBeansOfType(AbstractHandler.class).values()
                    .stream().collect(Collectors.groupingBy(AbstractHandler::getEventType));
        }
    
        /**
         * 数据分发到对应的事件处理
         */
        public void dispatch(JSONObject jsonObject){
            String code = (String) jsonObject.get("type");
            List<AbstractHandler> pushEventHandlers= this.routerMap.get(code);
            for (AbstractHandler pushEventHandler : pushEventHandlers) {
                log.info("开始处理{}事件",pushEventHandler.getEventType());
                pushEventHandler.handle(jsonObject);
            }
        }
    }
    
    

    4、测试

    package com.ylc;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import com.ylc.handle.AbstractHandler;
    import com.ylc.handle.PushEvent;
    import com.ylc.model.DeviceStatus;
    import com.ylc.model.eventEnum;
    import lombok.extern.slf4j.Slf4j;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    import java.util.List;
    
    @Slf4j
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class MessageHandleTest {
    
        @Autowired
        PushEvent pushEvent;
    
        @Test
        public void  testDeviceStatus(){
            DeviceStatus deviceStatus=new DeviceStatus();
            deviceStatus.setType(eventEnum.STATUS.getCode());
            deviceStatus.setUpdateTime(1653532367);
            deviceStatus.setState(1);
            JSONObject jsonObject= JSON.parseObject(JSONObject.toJSONString(deviceStatus));
            log.info("开始分发消息:{}",deviceStatus.toString());
            pushEvent.dispatch(jsonObject);
        }
    
    }
    
    

    image-20220423140135494

    • 如果有新的设备消息类型,只需要加一个新的事件处理类,其他代码不用变化,这样符合开放封闭原则还有单一原则,也增加了程序的灵活性。
    • 具体使用到哪个类型也不需要我们自己,交给程序运行时处理
    • 使用Map集合的方式,直接从集合里面根据特征找到对应的处理器,跟其他博客设置使用下一个处理者进行判断的方法类似,如果链条比较长那么使用下一个处理者方法不合适,需要从头遍历到尾部。
    • 还可以控制请求顺序,集合的话通过增加一个排序字段

    总结#

    责任链模式其实就是灵活的if..else..语句,将多个处理者连接成一条链。 接收到请求后, 它会 “询问” 每个处理者是否能够对其进行处理。 这样所有处理者都有机会来处理请求

    使用场景

    • 当必须按顺序执行多个处理者时,可以使用该模式
    • 如果所需处理者及其顺序必须在运行时进行改变, 可以使用责任链模式
    • 当程序需要使用不同方式处理不同种类请求,而且请求类型和顺序预先未知时,可以使用责任链模式
  • 相关阅读:
    给面试加点硬菜:延迟任务场景,该如何提高吞吐量和时效性!
    LeetCode C++ 67.二进制求和
    如何在Linux上搭建本地Docker Registry并实现远程连接
    智能门门锁记录的同步方案(离线恢复同步)
    得帆,Java一面,双非本
    math_极限&微分&导数&微商/对数函数的导数推导(导数定义极限法)
    第十九章总结
    2022年软件测试面试题大全【含答案】
    信息学奥赛一本通:1156:求π的值
    C# 反射(Reflection)详解-Assembly
  • 原文地址:https://www.cnblogs.com/cg-ww/p/16182356.html