专栏名称: Java知音
专注于Java,推送技术文章,热门开源项目等。致力打造一个有实用,有情怀的Java技术公众号!
目录
相关文章推荐
品牌几何  ·  万物皆可DeepSeek ·  2 天前  
51好读  ›  专栏  ›  Java知音

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

Java知音  · 公众号  ·  · 2025-03-05 10:05

正文

图片

前言

在 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 的结果。而在 MyBatis Plus 等框架中常用的 SFunction 是对 Lambda 表达式的进一步封装,使得我们可以更加灵活地操作实体类的属性。

实战演练:重构断言方法

下面的 ensureColumnValueValid 方法正是利用了函数式接口的魅力,实现了对任意实体类指定列值的有效性断言:

/**  
 * 确认数据库字段值有效(通用)  
 *   
 * @param  待验证值的类型  
 * @param valueToCheck 待验证的值  
 * @param columnExtractor 实体类属性提取函数  
 * @param queryExecutor 单条数据查询执行器  
 * @param errorMessage 异常提示信息模板  
 */
  
public static  void ensureColumnValueValid(V valueToCheck, SFunction columnExtractor, SFunction, T> queryExecutor, String errorMessage) {  
    if (valueToCheck == nullreturn;  
      
    LambdaQueryWrapper 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));  
}  

这个方法接受一个待验证的值、一个实体类属性提取函数、一个单行数据查询执行器和一个异常信息模板作为参数。通过这四个参数,不仅能够进行针对特定属性的有效性检查,而且还能生成具有一致性的异常信息。

对比分析

使用 Function 改造前
// 判断用户 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无效");  
    }     
}  
使用 Function 改造后
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());       
      
    // 创建订单的逻辑...  
}  

对比上述两段代码,我们发现后者不仅大幅减少了代码量,而且通过函数式编程,表达出更为清晰的逻辑意图,可读性和可维护性都有所提高。

优点
  • 减少重复代码: 通过 ensureColumnValueValid 方法,所有涉及数据库字段值有效性检查的地方都可以复用相同的逻辑,将变化的部分作为参数传递,大大减少了因特定校验逻辑而产生的代码量。

  • 增强代码复用: 抽象化的校验方法适用于多种场景,无论是用户ID、订单号还是其他任何实体属性的校验,一套逻辑即可应对。

  • 提升可读性和维护性: 通过清晰的函数签名和 Lambda 表达式,代码意图一目了然,降低了后续维护的成本。

  • 灵活性和扩展性: 当校验规则发生变化时,只需要调整 ensureColumnValueValid 方法或其内部实现,所有调用该方法的地方都会自动受益,提高了系统的灵活性和扩展性。

举一反三:拓展校验逻辑的边界

通过上述的实践,我们见识到了函数式编程在简化数据校验逻辑方面的威力。但这只是冰山一角,我们可以根据不同的业务场景,继续扩展和完善校验逻辑,实现更多样化的校验需求。以下两个示例展示了如何在原有基础上进一步深化,实现更复杂的数据比较和验证功能。

断言指定列值等于预期值

首先,考虑一个场景:除了验证数据的存在性,我们还需确认查询到的某列值是否与预期值相符。这在验证用户角色、状态变更等场景中尤为常见。为此,我们设计了 validateColumnValueMatchesExpected 方法:

/**  
 * 验证查询结果中指定列的值是否与预期值匹配  
 *  
 * @param              实体类型  
 * @param              目标列值的类型  
 * @param              查询条件列值的类型  
 * @param targetColumn    目标列的提取函数,用于获取想要验证的列值  
 * @param expectedValue   期望的列值  
 * @param conditionColumn 条件列的提取函数,用于设置查询条件  
 * @param conditionValue  条件列对应的值  
 * @param queryMethod     执行查询的方法引用,返回单个实体对象  
 * @param errorMessage    验证失败时抛出异常的错误信息模板  
 * @throws RuntimeException 当查询结果中目标列的值与预期值不匹配时抛出异常  
 */
  
public static  void validateColumnValueMatchesExpected(  
      SFunction targetColumn, R expectedValue,  
      SFunction conditionColumn, C conditionValue,  
      SFunction, T> queryMethod,  
      String errorMessage)
 
{  
  
   // 创建查询包装器,选择目标列并设置查询条件  
   LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();  
   wrapper.select(targetColumn);  
   wrapper.eq(conditionColumn, conditionValue);  
  
   // 执行查询方法  
   T one = queryMethod.apply(wrapper);  
   // 如果查询结果为空,则直接返回,视为验证通过(或忽略)  
   if (one == nullreturn;  
  
   // 获取查询结果中目标列的实际值  
   R actualValue = targetColumn.apply(one);  
  
   // 比较实际值与预期值是否匹配,这里假设notMatch是一个自定义方法用于比较不匹配情况  
   boolean doesNotMatch = notMatch(actualValue, expectedValue);  
   if (doesNotMatch) {  
      // 若不匹配,则根据错误信息模板抛出异常  
      throw new RuntimeException(String.format(errorMessage, expectedValue, actualValue));  
   }  
}  
  
// 假设的辅助方法,用于比较值是否不匹配,根据实际需要实现  
private static  boolean notMatch(R actual, R expected) {  
    // 示例简单实现为不相等判断,实际情况可能更复杂  
    return !Objects.equals(actual, expected);  
}  

这个方法允许我们指定一个查询目标列( targetColumn )、预期值( expectedValue )、查询条件列( conditionColumn )及其对应的条件值( conditionValue ),并提供一个查询方法( queryMethod )来执行查询。如果查询到的列值与预期不符,则抛出异常,错误信息通过 errorMessage 参数定制。







请到「今天看啥」查看全文