当前位置: 首页 > 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.xdnf.cn/news/1359811.html

相关文章:

  • [GeographicLib] LocalCartesian用法
  • 时序数据库选型“下半场”:从性能竞赛到生态博弈,四大主流架构深度横评
  • Palantir Foundry 领先其他数据平台5到10年:一位使用者的深入观察
  • 门面设计模式
  • 第4章 SPSS简介与数据库构建
  • 网络协议---TCP
  • 最大连续1的个数Ⅲ-滑动窗口
  • 2025/8/24 DockerDesktop安装使用
  • 【网络运维】Shell 脚本编程:while 循环与 until 循环
  • 审核问题——应用未配置图标的前景图和后景图
  • JUC——AQS
  • 客流特征识别误报率↓76%!陌讯多模态时序融合算法在智慧零售的实战解析
  • 蓝凌EKP产品:从 XML 到 JSON ——表单存储的性能优化实践
  • [自用笔记]上传本地项目至github
  • 【嵌入式开发 Linux 常用命令系列 8 -- git checkout 解冲突详细介绍】
  • Qt工具栏中图标槽函数没有响应的问题分析
  • 十一、redis 入门 之 数据持久化
  • 基于FPGA的情绪感知系统设计方案:心理健康监测应用(一)
  • yggjs_rlayout框架v0.1.2使用教程 01快速开始
  • 基于RBF-GA的铝/镁异材FSLW工艺参数优化研究
  • Qt---架构文件.pro
  • 02-开发环境搭建与工具链
  • 鸿蒙中点击响应时延分析
  • 多核多线程应用程序开发可见性和乱序如何处理
  • css3之flex布局
  • Linux 学习笔记 - 集群管理篇
  • 音视频学习(五十五):H264中的profile和level
  • pyecharts可视化图表-scatter:从入门到精通
  • Trip Footprint旅行足迹App
  • jar包项目自启动设置ubuntu