前言
在软件开发的道路上,设计模式一直被视为提升代码质量和可维护性的关键工具。然而,对于初学者而言,即使学习了定义,也时常感到“似懂非懂”,难以将其付诸实践。为了强化自我,我在此记录了从实习到工作(24届)期间,实际项目中接触到的一些不错的设计实践。希望通过这些案例,能够帮助自己更好地理解和应用设计模式,锻炼文章书写能力,也希望对其他初学者有所启发。
一、实战场景
场景描述
业务中需要对接A服务的路侧设备MQTT消息,在对应A服务代码的messageHandler中,会处理多个Topic的消息,例如设备状态信息 /mec/{mec_sn}/status
、路口事件信息 /mec/{mec_sn}/event
、信号灯信息 /rsu/{rsu_sn}/info
等一系列主题。
场景代码
代码如下(示例):
/**
* 设备消息处理器
*
* @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 ++
}
}
问题分析
- 在少量业务下,这样的代码使用还是简洁直观的。但是如果后续业务增加,逻辑复杂,每个Topic处理时,都会在MessageHandler中@Resource注入自己的业务类,以及书写自己的方法、方法再拆分方法,最终导致消息处理类代码量大并且杂乱,不满足单一职责原则。
- 代码中的 Pattern、Matcher 及相邻的 if (match.matches( )) 在每增加一个Topic需要再次进行编写,是否能抽离模板。
- 我们尝试进行策略抽离,但这里和标准的策略模式不同的是,标准策略对上下文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);
}
}
总结
以上就是初入开发的我在实践中遇到的一些问题,是目前能力下能达到的一些进阶,如果大家有更优雅的方式请多指教。