当前位置: 首页 > news >正文

Function + 枚举 + Map:轻量路由器的最佳实践

在这里插入图片描述

在很多项目里,都会遇到这种场景:

  • 有多种 类型(支付方式、发票类型、消息渠道……)。
  • 每种类型对应一段 处理逻辑
  • 调用方只需要根据类型找到对应逻辑去执行。

大多数人的第一反应是 if-elseswitch-case,再高级一点就是 策略模式。但我实际落地时发现,Function + 枚举 + Map 的组合可以更优雅地实现这类“轻量路由器”。


1. 基础写法:枚举绑定 Function

先回顾一下上一篇文章里的模式:在枚举里直接挂载 Function

public enum PayType {ALIPAY(req -> PayService.aliPay(req)),WECHAT(req -> PayService.wechatPay(req)),BANK(req -> PayService.bankPay(req));private final Function<PayRequest, PayResult> function;PayType(Function<PayRequest, PayResult> function) {this.function = function;}public PayResult process(PayRequest req) {return function.apply(req);}
}

这样已经能避免 switch,但问题来了:如果逻辑需要动态扩展? 枚举是写死的,新增一种类型必须改枚举本身,这在可插拔场景下不够灵活。


2. 升级写法:枚举做 Key,Map 管理 Function

这里就该 Map 登场了。我的做法是:枚举只定义 Key(语义清晰的常量),逻辑由 Map 挂载,随时可扩展

public enum BizType {ORDER,INVOICE,PAYMENT
}

配合一个路由器类:

public class BizRouter {private static final Map<BizType, Function<Object, Object>> ROUTER = new HashMap<>();// 注册逻辑static {ROUTER.put(BizType.ORDER, req -> OrderService.handle(req));ROUTER.put(BizType.INVOICE, req -> InvoiceService.handle(req));ROUTER.put(BizType.PAYMENT, req -> PaymentService.handle(req));}public static Object route(BizType type, Object req) {return ROUTER.getOrDefault(type, r -> {throw new UnsupportedOperationException("Unsupported biz type: " + type);}).apply(req);}
}

调用时:

Object result = BizRouter.route(BizType.ORDER, request);

这样就实现了一个 轻量路由器,逻辑和 Key 解耦。


3. 动态扩展:支持运行时注册

在一些“多租户 / 插件化”项目里,我们无法提前把所有逻辑写死到枚举中。于是我进一步扩展了这个模式:允许运行时注册新的处理器。

public class BizRouter {private static final Map<BizType, Function<Object, Object>> ROUTER = new ConcurrentHashMap<>();public static void register(BizType type, Function<Object, Object> function) {ROUTER.put(type, function);}public static Object route(BizType type, Object req) {return ROUTER.getOrDefault(type, r -> {throw new UnsupportedOperationException("Unsupported biz type: " + type);}).apply(req);}
}

调用时:

// 系统启动时动态注册
BizRouter.register(BizType.ORDER, req -> orderService.process(req));
BizRouter.register(BizType.INVOICE, req -> invoiceService.process(req));

这样,业务逻辑可以从配置文件、SPI 插件,甚至远程动态加载进来。


4. 高级玩法:支持降级与默认策略

有时候,某些类型逻辑可能失败,我们需要兜底。比如支付业务:某个渠道挂了,可以降级到“默认支付”或“失败处理器”。

在这个模式下,只需要稍微调整 Map 的默认值:

public static Object route(BizType type, Object req) {return ROUTER.getOrDefault(type, r -> DefaultHandler.handle(req)).apply(req);
}

或者用 二级 Map 做“主逻辑 + 降级逻辑”组合:

private static final Map<BizType, Function<Object, Object>> ROUTER = new ConcurrentHashMap<>();
private static final Map<BizType, Function<Object, Object>> FALLBACK = new ConcurrentHashMap<>();public static Object route(BizType type, Object req) {try {return ROUTER.get(type).apply(req);} catch (Exception e) {return FALLBACK.getOrDefault(type, r -> DefaultHandler.handle(req)).apply(req);}
}

这时候,枚举就是稳定的 Key,Function 是灵活的实现,Map 是天然的“路由表”。


5. 我的几点经验总结

  • 枚举负责约束,Map 负责扩展:这样既有编译期的安全性,又保留了运行时的灵活性。
  • 适合做路由器/调度器:支付方式、发票类型、消息处理、文件导入导出等,都很适合这个模式。
  • 比策略模式轻量:不需要为每个策略写一个类,用 Function 就够了。
  • 别让 Function 太臃肿:复杂逻辑还是要放到 Service 层,枚举 + Map 只是做路由。
  • 注意并发安全:如果涉及运行时动态注册,Map 要用 ConcurrentHashMap,避免线程安全问题。

结语

这套 Function + 枚举 + Map 的组合,我在多个系统里都用过:

  • 在开票系统里,用它来调度不同的税务局接口;
  • 在支付系统里,用它路由不同的支付渠道;
  • 在数据同步系统里,用它切换不同的序列化/反序列化协议。

它不是银弹,但在需要“固定枚举值 + 灵活逻辑”的场景里,往往比传统设计模式更实用。

推荐阅读文章

  • 由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)

  • 如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系

  • HTTP、HTTPS、Cookie 和 Session 之间的关系

  • 什么是 Cookie?简单介绍与使用方法

  • 什么是 Session?如何应用?

  • 使用 Spring 框架构建 MVC 应用程序:初学者教程

  • 有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误

  • 如何理解应用 Java 多线程与并发编程?

  • 把握Java泛型的艺术:协变、逆变与不可变性一网打尽

  • Java Spring 中常用的 @PostConstruct 注解使用总结

  • 如何理解线程安全这个概念?

  • 理解 Java 桥接方法

  • Spring 整合嵌入式 Tomcat 容器

  • Tomcat 如何加载 SpringMVC 组件

  • “在什么情况下类需要实现 Serializable,什么情况下又不需要(一)?”

  • “避免序列化灾难:掌握实现 Serializable 的真相!(二)”

  • 如何自定义一个自己的 Spring Boot Starter 组件(从入门到实践)

  • 解密 Redis:如何通过 IO 多路复用征服高并发挑战!

  • 线程 vs 虚拟线程:深入理解及区别

  • 深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别

  • 10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!

  • “打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!”

  • Java 中消除 If-else 技巧总结

  • 线程池的核心参数配置(仅供参考)

  • 【人工智能】聊聊Transformer,深度学习的一股清流(13)

  • Java 枚举的几个常用技巧,你可以试着用用

  • 由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)

  • 如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系

  • HTTP、HTTPS、Cookie 和 Session 之间的关系

  • 使用 Spring 框架构建 MVC 应用程序:初学者教程

  • 有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误

  • Java Spring 中常用的 @PostConstruct 注解使用总结

  • 线程 vs 虚拟线程:深入理解及区别

  • 深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别

  • 10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!

  • 探索 Lombok 的 @Builder 和 @SuperBuilder:避坑指南(一)

  • 为什么用了 @Builder 反而报错?深入理解 Lombok 的“暗坑”与解决方案(二)

http://www.dtcms.com/a/348005.html

相关文章:

  • ERROR 2003 (HY000): Can‘t connect to MySQL server on ‘192.168.24.96‘ (10060)
  • 基于Java、GeoTools与PostGIS的对跖点求解研究
  • 大数据毕业设计选题推荐:基于Spark+Django的学生创业数据分析可视化系统详解 毕业设计/选题推荐/深度学习/数据分析/数据挖掘/机器学习/随机森林
  • 网络编程socket-Udp
  • Linux网络启程
  • Java基础(十四)分布式
  • 《Distilling the Knowledge in a Neural Network》论文PDF分享, 2015 年,谷歌提出了 “知识蒸馏” 的概念
  • 深入解析Apache Kafka的核心概念:构建高吞吐分布式流处理平台
  • 07-分布式能力与多设备协同
  • Lucene 与 Elasticsearch:从底层引擎到分布式搜索平台的演进
  • Flink提交作业
  • (Redis)内存淘汰策略
  • Elastic APM vs Apache SkyWalking vs Pinpoint:APM性能监控方案对比分析与最佳实践
  • 深度学习之第二课PyTorch与CUDA的安装
  • 华为云Stack环境中计算资源,存储资源,网络资源发放前的准备工作(上篇)
  • 【软考架构】云计算相关概念
  • 嵌入式系统bringup通用流程
  • SpringBoot的学生学习笔记共享系统设计与实现
  • 鸿蒙中应用闪屏解决方案
  • 从Android到鸿蒙:一场本应无缝的转型-优雅草卓伊凡
  • Flink 实时加购数据“维表补全”实战:从 Kafka 到 HBase 再到 Redis 的完整链路
  • 设计模式:工厂模式
  • 3.4 磁盘存储器 (答案见原书 P194)
  • STM32H723Zx OCTO-SPI(OSPI) 读写 W25Q64
  • fastmcp 客服端远程MCP服务调用;多工具 MCP服务情景案例;集成fastapi服务
  • Vue.js 核心机制深度学习笔记
  • 阿里云 OSS 前端直传实战:表单上传 + Policy 模式详解
  • 基于魔搭社区与阿里云百炼的云端 RAG 私有知识问答系统实现
  • GHOST 小巧方便的 WinXP !
  • 华为网路设备学习-30(BGP协议 五)Community、