利用 Function 接口告别冗余(屎山)代码

前言
在 Java 开发的征途中,我们时常与重复代码不期而遇。这些重复代码不仅让项目显得笨重,更增加了维护成本。幸运的是,Java 8 带来了函数式编程的春风,以 Function 接口为代表的一系列新特性,为我们提供了破除这一难题的利剑。本文将以一个实际应用场景为例,即使用 Java 8 的函数式编程特性来重构数据有效性断言逻辑,展示如何通过 SFunction(基于 Java 8 的 Lambda 表达式封装)减少代码重复,从而提升代码的优雅性和可维护性。
背景故事
数据校验的烦恼想象一下,在一个复杂的业务系统中,我们可能需要频繁地验证数据库中某个字段值是否有效,是否符合预期值。传统的做法可能充斥着大量相似的查询逻辑,每次都需要手动构建查询条件、执行查询并处理结果,这样的代码既冗长又难以维护。例如以下两个验证用户 ID 和部门 ID 是否有效的方法,虽然简单,但每次需要校验不同实体或不同条件时,就需要复制粘贴并做相应修改,导致代码库中充满了大量雷同的校验逻辑,给维护带来了困扰。

// 判断用户 ID 是否有效
public void checkUserExistence(String userId) {
    User user = userDao.findById(userId);
    if (user == null) {
        throw new RuntimeException("用户ID无效");
    }
}

// 判断部门 ID 是否有效
public void checkDeptExistence(String deptId) {
    Dept dept = deptDao.findById(deptId);
    if (dept == null) {
        throw new RuntimeException("部门ID无效");
    }   
}

Java 8 的魔法棒
函数式接口Java 8 引入了函数式接口的概念,其中 Function<T, R> 是最基础的代表,它接受一个类型 T 的输入,返回类型 R 的结果。而在 MyBatis Plus 等框架中常用的 SFunction 是对 Lambda 表达式的进一步封装,使得我们可以更加灵活地操作实体类的属性。
实战演练
重构断言方法下面的 ensureColumnValueValid 方法正是利用了函数式接口的魅力,实现了对任意实体类指定列值的有效性断言:

/**
 * 确认数据库字段值有效(通用)
 * 
 * @param <V> 待验证值的类型
 * @param valueToCheck 待验证的值
 * @param columnExtractor 实体类属性提取函数
 * @param queryExecutor 单条数据查询执行器
 * @param errorMessage 异常提示信息模板
 */
public static <T, R, V> void ensureColumnValueValid(V valueToCheck, SFunction<T, R> columnExtractor, SFunction<LambdaQueryWrapper<T>, T> queryExecutor, String errorMessage) {
    if (valueToCheck == nullreturn;
    
    LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<>();
    wrapper.select(columnExtractor);
    wrapper.eq(columnExtractor, valueToCheck);
    wrapper.last("LIMIT 1");

    T entity = queryExecutor.apply(wrapper);
    R columnValue = columnExtractor.apply(entity);
    if (entity == null || columnValue == null)
        throw new DataValidationException(String.format(errorMessage, valueToCheck));
}

改造后使用如下代码所示:

 

public void assignTaskToUser(AddOrderDTO dto) {
    ensureColumnValueValid(dto.getUserId(), User::getId, userDao::getOne, "用户ID无效");
    ensureColumnValueValid(dto.getDeptId(), Dept::getId, deptDao::getOne, "部门ID无效");    
    ensureColumnValueValid(dto.getCustomerId(), Customer::getId, customerDao::getOne, "客户ID无效");
    ensureColumnValueValid(dto.getDeptId(), Dept::getId, deptDao::getOne, "部门ID无效");
    ensureColumnValueValid(dto.getSupplieId(), Supplie::getId, supplierDao::getOne, "供应商ID无效");

    // 现在可以确信客户存在
    Customer cus = customerDao.findById(dto.getCustomerId());     
    
    // 创建订单的逻辑...
}

再如下例子:

  // 优化前  
public void validateUser(User user) { if (user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException("Name cannot be empty"); } if (user.getAge() < 18) { throw new IllegalArgumentException("Age must be at least 18"); } if (user.getEmail() == null || !user.getEmail().matches("regex")) { throw new IllegalArgumentException("Invalid email address"); } } // 优化后 public static <T> void validate(T obj, Function<T, Boolean> validator, String errorMessage) { if (!validator.apply(obj)) { throw new IllegalArgumentException(errorMessage); } } public static void validateUser2(User user) { validate(user, u -> u.getName() != null && !u.getName().isEmpty(), "Name cannot be empty"); validate(user, u -> u.getAge() >= 18, "Age must be at least 18"); validate(user, u -> u.getEmail() != null && u.getEmail().matches("regex"), "Invalid email address"); }

 

参考博客:利用 Function 接口告别冗余(屎山)代码

 

posted @ 2024-12-02 14:42  郭慕荣  阅读(18)  评论(0编辑  收藏  举报