Java函数式命令模式
轻量级跨包调用解耦方案,让跨包调用就像调用本地接口那样简单。适用与具有公共依赖的两个jar包,但是又不想相互引入对方作为依赖。
函数式命令模式,很好地实现了跨包调用解耦的目标,并且通过泛型+JSON动态转换保证了灵活性。
设计决策:
特性 | 实现效果 | 优势 |
跨包调用 | 通过字符串Key调用,完全消除编译期依赖 | 模块完全隔离 |
类型安全 | 泛型+ Class<T> 确保参数类型正确 | 避免运行时类型错误 |
轻量简洁 | 无状态、无复杂继承结构,直接函数式调用 | 比传统命令模式更简洁 |
JSON兼容 | 天然支持RPC/微服务改造(未来如需) | 轻松改造成RPC调用 |
常量命令:
public interface Command { String CMD_MENU_SAVE = "menu.save"; } |
命令操作系统:
public abstract class OperateSystem { private static final Map<String, Function<JSONObject, ?>> COMMAND_MAP = new HashMap<>(); public static <T, R> void registerCommand(String command, Class<T> paramType, Function<T, R> receiver) { COMMAND_MAP.put(command, json -> { T param = json.toJavaObject(paramType); if (ObjectUtils.isEmpty(param)) { throw new CommonException("Parameter parsing failed, expected type: %s", paramType.getName()); } String message = BeanUtils.parseEmptyField(param); if (StringUtils.isNotBlank(message)) { throw new CommonException("Parameter validation failed: %s", message); } return receiver.apply(param); }); } public static void unregisterCommand(String command) { COMMAND_MAP.remove(command); } public static <R> R execute(String command, JSONObject param) { Function<JSONObject, R> function = getJsonFunction(command); return function.apply(param); } @SuppressWarnings("unchecked") private static <R> Function<JSONObject, R> getJsonFunction(String command) { Function<JSONObject, R> function = (Function<JSONObject, R>) COMMAND_MAP.get(command); if (ObjectUtils.isEmpty(function)) { throw new CommonException("Unregistered command: %s", command); } return function; } @SuppressWarnings("unchecked") public static <R, E> E execute(String command, Class<E> resultType, JSONObject param) { if (ObjectUtils.isEmpty(resultType)) { throw new CommonException("Command result type can not be null: %s", command); } Function<JSONObject, R> function = getJsonFunction(command); R result = function.apply(param); if (ObjectUtils.isEmpty(result)) { return null; } if (resultType.isInstance(result)) { return (E)result; } throw new CommonException("Command [%s] failed to produce the expected result." + " Expected type: %s, Actual type: %s", command, resultType.getName(), result.getClass().getName()); } } |
上述两个代码放在公共依赖包中,如common.jar。
模块接收者(引入common.jar依赖):
@PostConstruct // 依赖注入完成后初始化函数 private void registerCommand() { OperateSystem.registerCommand(Command.CMD_MENU_SAVE, SaveMenu.class, this::saveMenu); } public Boolean saveMenu(SaveMenu saveMenu) { ... ... } |
模块调用者(引入common.jar依赖):
public void analyseBuildCache() { ... ... String rootPath = this.engineer.getInterfaceRootPath(); JSONObject param = new JSONObject(); param.put("title", rootPath); param.put("code", StringUtils.absoluteLowerUnderlineLetter(rootPath)); param.put("routePath", rootPath); String webPackagePath = this.engineer.getWebPackagePath(); param.put("componentPath", webPackagePath.substring(6)); param.put("sort", 1); OperateSystem.execute(Command.CMD_MENU_SAVE, param); } |
对比传统方法:
方案 | 当前实现 | Spring依赖注入 | RPC调用 |
跨包隔离 | ✅ 完全隔离 | ❌ 需暴露接口 | ✅ 隔离 |
类型安全 | ✅ 运行时校验 | ✅ 编译期检查 | ❌ 需额外契约 |
代码量 | ⭐ 极少 | ⭐⭐ 中等 | ⭐⭐⭐ 复杂 |
性能 | ⭐⭐⭐ 直接调用 | ⭐⭐⭐ 直接调用 | ⭐ 网络开销 |
适用场景
推荐使用:
✅ 模块化架构中的跨包调用;
✅ 需要快速开发且避免依赖传染的场景;
✅ 未来可能改为微服务但不想提前引入复杂设计。
不推荐使用:
❌ 需要编译期强类型检查(考虑接口下沉)。
❌ 需要支持事务/重试等复杂控制流(考虑Spring事务代理)。
命令模式核心:
"将请求封装为对象,从而使你可以参数化其他对象使用不同的请求"
----《设计模式:可复用面向对象软件的基础》
本文为作者(难拳)原创,转载请注明出处。