Function + 枚举 + Map:轻量路由器的最佳实践
在很多项目里,都会遇到这种场景:
- 有多种 类型(支付方式、发票类型、消息渠道……)。
- 每种类型对应一段 处理逻辑。
- 调用方只需要根据类型找到对应逻辑去执行。
大多数人的第一反应是 if-else 或 switch-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 的“暗坑”与解决方案(二)