【浅谈设计模式】(16):责任链模式 | 物联网案例入门责任链

前言

之前在做规则引擎的相关内容,了解了很多优秀的框架,比如 Droos,URule、LiteFlow等,其中就有责任链模式的引用,今天就以物联网采集数据的例子来参考学习责任链模式的应用,通过第一个版本入门责任链模式,并通过第二个版本加迭代器模式完成优化。

一、概述

1.1 入门

责任链模式是一种行为设计模式, 允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。

责任链模式,也能像我们之前学的策略模式一样消除 if else。

但它的特定是一条链路,类似下面这种,为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

责任链模式中常见的就是,一个请求需要多个对象来处理,然而每个对象处理的权限和功能不一样,比如请假流程,直接上级,部门负责人,总经理等的授权处理不一样,三天内的直接上级就能批准,流程不会发送到其他人,而三天以上的流程可能就需要部门负责人来批准了。

再比如,掘金文章的发布,可能会经过一些敏感词的限制,这就是一个责任链对象,文章大小限制可能也是个处理对象,图片大小处理等工作,这块又可以做成责任链模式,不同的对象负责处理不同的业务。

1.2 代码举例

相信上面的例子可以初步了解到责任链的用处了,那结合代码,如果不用责任链模式,我们会怎么写?

csharp 复制代码
public  void test(String rule) {

    if(rule.equals("image")){
        Handler1.process();
    }else if(rule.equals("word")){
        Handler2.process();
    }else if(rule.equals("size")){
        Handler3.process();
    }
    ......
}

可以看到一大堆 if else 又来了,判断条件太多,代码也越来越臃肿,且不能改变判断的顺序,而责任链模式是如何处理的呢?

思路 :业务逻辑应该改成这样,如果满足条件 1,则 Handler1 处理,不满足则向下传递,如果条件 2 满足,则 Handler2 处理,不满足,则继续调用下一个 Handler,直到所有的 Handler 都判断完,处理的逻辑就是将判定条件放到处理类中,并组装 Handler 的顺序,Handler1 处理完,判断下是否存在下一个 Handler,如果有则进入下一个 Handler 的处理。

1.3 重要概念

业务逻辑应该梳理下:

责任链模式主要包含三个角色:

  • 客户端:创建处理链,向具体处理者对象提交请求,不用关心处理细节和请求的传递过程。
  • 抽象处理者角色:定义一个处理请求的接口,包含抽象处理方法,子类来实现具体的抽象处理方法,并提供一个方法设置下一个处理者
  • 具体处理者:实现具体的处理方法即可,如果能处理就处理,不然就将处理交给下一个 Handler

二、案例实现

我们以物联网数据采集的规则引擎案例作为 Demo 演示。

比如温度传感器的数据采集上来到我们责任链了,我们要对数据进行多个处理操作:

  • 1、数据是否存储入库?;
  • 2、如果温度大于最大阈值,发告警短信;
  • 3、如果温度小于最小阈值,存储告警消息;
  • 4、如果该设备是热点设备,直接存储 redis;
  • 5、如果该设备要做规则联动,则调用 RPC 服务;
  • ..............

不用责任链模式,我们就会写一大堆的 if-else,下面我将通过两个版本完成规则链路的演示实现。

2.1 数据采集案例 V1 版本

1、client 角色,入口,组装了三个 Handler 的执行顺序。

less 复制代码
@RestController
@RequestMapping
public class IotSendController {

    @Autowired
    private AlarmHandler alarmHandler;
    @Autowired
    private HotHandler hotHandler;
    @Autowired
    private SaveHandler saveHandler;
    @PostMapping("/iot")
    public String getV(@RequestBody DevicePoint devicePoint){
        saveHandler.setNextHandler(alarmHandler);
        alarmHandler.setNextHandler(hotHandler);
        saveHandler.processPointValue(devicePoint);
        return "处理结束";
    }
}

2、设备模型

typescript 复制代码
public class DevicePoint {
    String deviceName;
    // 是否是热点数据;1是
    int isHot;
    BigDecimal value;

    public String getDeviceName() {
        return deviceName;
    }

    public void setDeviceName(String deviceName) {
        this.deviceName = deviceName;
    }

    public int getIsHot() {
        return isHot;
    }

    public void setIsHot(int isHot) {
        this.isHot = isHot;
    }

    public BigDecimal getValue() {
        return value;
    }

    public void setValue(BigDecimal value) {
        this.value = value;
    }
}

3、抽象 handler

arduino 复制代码
public abstract class DeviceHandler {
    protected final static String rule_one = "save"; // 数据入库存储
    protected final static String rule_tow = "alarm"; // 告警短信发送
    protected final static String rule_three = "hot";// 存入热点数据


    // 设置下一个处理者
    private DeviceHandler nextHandler;

    //设置下一个 Handler
    public void setNextHandler(DeviceHandler nextHandler){
        this.nextHandler = nextHandler;
    }

    //处理 IOT 上传的采集数据
    public final void processPointValue(DevicePoint point){   
        this.handlePoint(point);

        //如果还有下一个Handler
        if(null != this.nextHandler){
            this.nextHandler.processPointValue(point); //继续提交
        } else {
            System.out.println("采集结束");
        }
    }
    
    protected abstract void handlePoint(DevicePoint leave);
}

4、具体 Handler

scala 复制代码
@Component("save")
public class SaveHandler extends DeviceHandler{

    public SaveHandler() {
        super(DeviceHandler.rule_one);
    }

    @Override
    public void setNextHandler(DeviceHandler nextHandler) {
        super.setNextHandler(nextHandler);
    }

    @Override
    protected void handlePoint(DevicePoint point) {
        System.out.println("数据入库操作!");
    }
}
scala 复制代码
@Component("hot")
public class HotHandler extends DeviceHandler{
    public HotHandler() {
        super(DeviceHandler.rule_three);
    }

    @Override
    public void setNextHandler(DeviceHandler nextHandler) {
        super.setNextHandler(nextHandler);
    }

    @Override
    protected void handlePoint(DevicePoint point) {
        System.out.println("热点数据哦!去 redis 吧");
    }
}
scala 复制代码
@Component("alarm")
public class AlarmHandler extends DeviceHandler{

    public AlarmHandler() {
        super(DeviceHandler.rule_tow);
    }

    @Override
    public void setNextHandler(DeviceHandler nextHandler) {
        super.setNextHandler(nextHandler);
    }

    @Override
    protected void handlePoint(DevicePoint point) {
        if(point.getValue().compareTo(new BigDecimal(50)) > 0){
            System.out.println("发送短信通知,告警超过阈值了!");
        }
    }
}

5、测试,发送请求

json 复制代码
{
    "deviceName":"温度传感器",
    "isHot": 1,
    "value": 101
}

分别操作 saveHandler、AlarmHandler、HotHandler,这个是根据我们的组装顺序完成的,测试完成了。

2.2 数据采集案例 V2 版本

在上一个版本中,我们发现存在什么问题呢?就是需要人为的指定 Handler 的传递顺序,如果我有先后顺序的要求呢,我想执行后续的 Handler,那岂不是要重改代码了,别慌,迭代器模式来了,虽然目前还没学到, 但是可以先来用用看,就是 for 循环而已, 优化我们的第二版代码,实现顺序的可控性,同时可以实现按需加载对应的处理器。

1、客户端

less 复制代码
@RestController
@RequestMapping
public class Iot2SendController {

    @Autowired
    private DeviceService deviceService;
    @PostMapping("/v2/iot")
    public String getV(@RequestBody DevicePoint devicePoint){
        deviceService.reviewDevice(devicePoint);
        return "处理结束";
    }
}

2、领域模型,添加一个 rule 字段

typescript 复制代码
public class DevicePoint {
    String deviceName;
    // 是否是热点数据;1是
    int isHot;
    BigDecimal value;
    String rule;

    public String getDeviceName() {
        return deviceName;
    }

    public void setDeviceName(String deviceName) {
        this.deviceName = deviceName;
    }

    public int getIsHot() {
        return isHot;
    }

    public void setIsHot(int isHot) {
        this.isHot = isHot;
    }

    public BigDecimal getValue() {
        return value;
    }

    public void setValue(BigDecimal value) {
        this.value = value;
    }

    public String getRule() {
        return rule;
    }

    public void setRule(String rule) {
        this.rule = rule;
    }
}

3、处理器:都维持原样。

4、迭代器模式:for 循环引入

scss 复制代码
@Service
public class DeviceService {
    @Autowired
    ApplicationContext applicationContext;

    public void reviewDevice(DevicePoint point){
        List<DeviceHandler> deviceHandlers =new ArrayList<>();

        for (String name : point.getRule().split(",")) {
            deviceHandlers.add((DeviceHandler) applicationContext.getBean(name));
        }

        for (DeviceHandler deviceHandler : deviceHandlers) {
            deviceHandler.processPointValue(point);
        }
    }
}

5、测试

1)发送请求

json 复制代码
{
    "deviceName":"温度传感器",
    "isHot": 1,
    "value": 101,
    "rule":"alarm,save"
}

2)切换顺序,然后我们在加个 filter

json 复制代码
{
    "deviceName":"温度传感器",
    "isHot": 1,
    "value": 101,
    "rule":"hot,alarm,save"
}

V2 版本中,通过迭代器模式+责任链模式完成动态的规则加载和顺序的把控。

三、扩展知识

3.1 优缺点

优点:

  • 责任链模式降低了对象之间的耦合度,减少了 if-else 的编写
  • 增强了系统的可扩展性
  • 按职责加载不同的处理器,非常灵活
  • 符合类的单一职责原则,每个 Handler 处理自己分内的事情

缺点:

  • 不能保证每个 Handler 都能被处理,可能每个 Handler 都忽略了它
  • 处理涉及多个对象,系统性能受到一定的影响
  • 责任链如果设置错误,可能会出现循环问题,比如 Ahandler 指定下一个处理器是 B,但是 B 不小心指定下一个处理器是 A。

3.2 源码运用

在我们的 Filter 过滤器中是典型应用的。例如 SpringSecurityFilter 中会默认执行几十个 FIlter,不同的 Filter 做不同的过滤操作。

FilterChain 就是一条过滤链,其中每个过滤器都可以决定是否执行下一步。

总结

责任链模式是减少 if-else 又一个方式,它将判断条件放到各个处理器中,非常灵活了,不过要注意不同 Handler 的调用顺序,避免出现循环调用问题。

OK ,又搞定一个~

相关推荐
一只叫煤球的猫1 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz9652 小时前
tcp/ip 中的多路复用
后端
bobz9652 小时前
tls ingress 简单记录
后端
皮皮林5513 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友3 小时前
什么是OpenSSL
后端·安全·程序员
bobz9653 小时前
mcp 直接操作浏览器
后端
前端小张同学6 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook6 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康7 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在7 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net