重学设计模式之理论到实践:策略模式-MQTT消息处理


前言

 在软件开发的道路上,设计模式一直被视为提升代码质量和可维护性的关键工具。然而,对于初学者而言,即使学习了定义,也时常感到“似懂非懂”,难以将其付诸实践。为了强化自我,我在此记录了从实习到工作(24届)期间,实际项目中接触到的一些不错的设计实践。希望通过这些案例,能够帮助自己更好地理解和应用设计模式,锻炼文章书写能力,也希望对其他初学者有所启发。



一、实战场景

场景描述

 业务中需要对接A服务的路侧设备MQTT消息,在对应A服务代码的messageHandler中,会处理多个Topic的消息,例如设备状态信息 /mec/{mec_sn}/status路口事件信息 /mec/{mec_sn}/event信号灯信息 /rsu/{rsu_sn}/info等一系列主题。

Code

场景代码

代码如下(示例):

/**
 * 设备消息处理器
 *
 * @author FRSF
 * @since 2024/11/29 14:49
 */
public class DeviceMessageHandler implements MessageHandler {

    // 放成员中,类加载编译一次即可
    private static final Pattern MEC_STATUS = Pattern.compile("^/mec/(\\d+)/status$");
    private static final Pattern MEC_EVENT = Pattern.compile("^/mec/(\\d+)/event$");
    private static final Pattern RUS_INFO = Pattern.compile("^/rus/(\\d+)/info$");

    @Override
    @ServiceActivator(inputChannel = "mqttInputHaiKChannel")
    public void handleMessage(Message<?> message) throws MessagingException {
        // 获取topic及payload
        String topic = message.getHeaders().get("mqtt_receivedTopic").toString();
        JSONObject entries = JSONUtil.parseObj(message.getPayload());

        // 匹配设备状态
        Matcher mecStatusMatcher = MEC_STATUS.matcher(topic);
        if (mecStatusMatcher.matches()) {
            String mecSn = mecStatusMatcher.group(1);
            doMecStatusService(mecSn, entries);
            return;
        }

        // 匹配设备事件
        Matcher mecEventMatcher = MEC_EVENT.matcher(topic);
        if (mecEventMatcher.matches()) {
            String mecSn = mecEventMatcher.group(1);
            doMecEventService(mecSn, entries);
            return;
        }

        // 匹配信号灯信息
        Matcher rusInfoMatcher = RUS_INFO.matcher(topic);
        if (rusInfoMatcher.matches()) {
            String rusSn = rusInfoMatcher.group(1);
            doRusInfoService(rusSn, entries);
        }

    }

    private void doRusInfoService(String rusSn, JSONObject entries) {
		// 多依赖注入及业务处理
		// @resource++
		// code line ++
	}

    private void doMecEventService(String mecSn, JSONObject entries) {
		// 多依赖注入及业务处理
		// @resource++
		// code line ++
	}

    private void doMecStatusService(String topic, JSONObject entries) {
		// 多依赖注入及业务处理
		// @resource++
		// code line ++
	}
}

问题分析

  1. 在少量业务下,这样的代码使用还是简洁直观的。但是如果后续业务增加,逻辑复杂,每个Topic处理时,都会在MessageHandler中@Resource注入自己的业务类,以及书写自己的方法、方法再拆分方法,最终导致消息处理类代码量大并且杂乱,不满足单一职责原则。
  2. 代码中的 Pattern、Matcher 及相邻的 if (match.matches( )) 在每增加一个Topic需要再次进行编写,是否能抽离模板。
  3. 我们尝试进行策略抽离,但这里和标准的策略模式不同的是,标准策略对上下文Context进行get操作就能精确匹配对应的处理算法,而这里的topic并不是不变的,而是带有可变的设备SN,所以无法进行精确匹配,如何进行优雅Code呢。



二、代码优化

策略优化-抽象类

  既然每个Topic都无可避免地引入了Pattern、Matchar以及IF逻辑,那么我们就将其封装到各自策略类中。每次IF逻辑都是类似的判断逻辑,变化的是不同的Pattern,那么判断逻辑我们可以放入抽象类的模板方法中,最终抽象出Pattern的获取以及handler业务方法即可。策略扫描我直接使用Spring扫描注入List即可,再进行循环判断,代码借用了一些javax.servlet.Filter的思想,话不多说,直接Coding🚴‍♀️。

/**
 * 设备上报消息处理策略抽象类
 *
 * @author He Kuntao
 * @since 2024/11/28 16:49
 */
public abstract class AbstractHaikDeviceStrategy {

    /**
     * 获取匹配的正则表达式
     *
     * @return Pattern
     */
    protected abstract Pattern getPattern();

    /**
     * 消息处理
     *
     * @param topic 消息主题
     * @param entries 消息负载
     */
    public abstract void handleMessage(String topic, JSONObject entries);

	/**
	 * 判断是否支持该策略模板方法
	 *
	 * @param topic 消息主题
	 * @return boolean
	 */
	public final Matcher support(String topic) {
	  Pattern pattern = Objects.requireNonNull(getPattern(), "Pattern must not be null");
	  Matcher matcher = pattern.matcher(topic);
	  if (matcher.matches()) {
	    return matcher;
	  }
	  return null;
	}
}
/**
 * RSU信号灯策略类
 *
 * @author He Kuntao
 * @since 2024/11/28 16:49
 */
@Component
public class RsuInfoStrategy extends AbstractHaikDeviceStrategy {

	private static final Pattern RUS_INFO = Pattern.compile("^/rus/(\\d+)/info$");

    @Override
    protected Pattern getPattern() {
        return RUS_STATUS_TOPIC;
    }

    @Override
	public void handleMessage(String topic, JSONObject entries, Matcher matcher) {
    	String rsuSn = matcher.group(1);
        doService()
    }
}
/**
 * 设备消息处理器
 *
 * @author FRSF
 * @since 2024/11/29 14:49
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class DeviceMessageHandler  implements MessageHandler {

    /** 策略扫描注入 */
    @Resource
    private List<AbstractHaikDeviceStrategy> haikDeviceStrategieList = new ArrayList<>();

    @Override
    @ServiceActivator(inputChannel = "mqttInputHaiKDeviceChannel")
    public void handleMessage(Message<?> message) throws MessagingException {
        // 获取topic
        String topic = Objects.requireNonNull(message.getHeaders().get("mqtt_receivedTopic")).toString();
        JSONObject entries = JSONUtil.parseObj(message.getPayload());

        // 策略匹配
        for (AbstractHaikDeviceStrategy haikDeviceStrategy : haikDeviceStrategieList) {
			Matcher support = haikDeviceStrategy.support(topic);
			if (support != null) {
			  haikDeviceStrategy.handleMessage(topic, entries, support);
			  break;
			}
        }
    }
}

策略优化-HashMap

  在我以为设计得还行的情况下,通义灵码给了我一个最佳实践👍。这里利用了一个Pattern对应一个MessageHandler,直接用HashMap<Pattern, MessageHandler>存储了所有策略,再进行keySet遍历判断处理代码简介了不少的。

/**
 * 策略处理接口
 *
 * @author FRSF
 * @since 2024/11/29 14:49
 */
public interface MessageHandler {
	void handleMessage(String topic, JSONObject entries);
}
/**
 * 策略实现类
 *
 * @author FRSF
 * @since 2024/11/29 14:49
 */
public class Topic1Handler implements MessageHandler {
    @Override
    public void handleMessage(String message, String deviceId) {
        System.out.println("Handling message for topic1/" + deviceId + ": " + message);
        // 具体处理逻辑
    }
}
/**
 * 策略上下文
 *
 * @author FRSF
 * @since 2024/11/29 14:49
 */
public class MessageHandlerContext {
    private Map<Pattern, MessageHandler> handlerMap;

    public MessageHandlerContext() {
        handlerMap = new HashMap<Pattern, HandlerMessage>();

        // 初始化策略映射
        handlerMap.put(Pattern.compile("^topic1/(\\w+)$"), new Topic1Handler());
        handlerMap.put(Pattern.compile("^topic2/(\\w+)$"), new Topic2Handler());
    }

    public void handleMessage(String topic, JSONObject payload) {
        for (Map.Entry<Pattern, MessageHandler> entry : handlerMap.entrySet()) {
            Pattern pattern = entry.getKey();
            Matcher matcher = pattern.matcher(topic);
            if (matcher.matches()) {
                String deviceId = matcher.group(1);
                entry.getValue().handleMessage(message, payload);
                return;
            }
        }
        System.out.println("No handler found for topic: " + topic);
    }
}


总结

  以上就是初入开发的我在实践中遇到的一些问题,是目前能力下能达到的一些进阶,如果大家有更优雅的方式请多指教。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值