侧边栏壁纸
博主头像
蚌埠住了捏博主等级

快乐,健康,自由,强大

  • 累计撰写 55 篇文章
  • 累计创建 12 个标签
  • 累计收到 21 条评论

目 录CONTENT

文章目录

20241117基于函数式接口编程的业务代码重构实践

蚌埠住了捏
2024-11-17 / 0 评论 / 1 点赞 / 52 阅读 / 1,580 字

函数式编程当然已经非常普遍了,对于Java而言,异步编程和流式编程都是常见的应用场景,lambda表达式没少用。今天我要分享的是基于函数式接口自定义业务模板的实践。

我们首先回顾下函数式编程

函数式编程是一种编程范式。它把计算当成是数学函数的求值,从而避免改变状态和使用可变数据。它是一种声明式的编程范式,通过表达式和声明而不是语句来编程。

函数式编程接口

接口名称 特征 用途 方法名
Supplier<T> 无参数,返回一个结果 获取值或实例,工厂模式,延迟计算 T get()
Comparator<T> 接受两个参数,返回 int 比较两个对象,用于排序和比较操作 int compare(T o1, T o2)
Consumer<T> 接受一个参数,无返回值 执行操作,通常是副作用操作 void accept(T t)
BiConsumer<T, U> 接受两个参数,无返回值 执行操作,通常是副作用操作 void accept(T t, U u)
Function<T, R> 接受一个参数,返回一个结果 将输入转换为输出,如数据转换或映射 R apply(T t)
BiFunction<T, U, R> 接受两个参数,返回一个结果 将两个输入转换为输出 R apply(T t, U u)
Predicate<T> 接受一个参数,返回 boolean 测试参数是否满足特定条件 boolean test(T t)
BiPredicate<T, U> 接受两个参数,返回 boolean 测试两个参数是否满足特定条件 boolean test(T t, U u)
UnaryOperator<T> 接受一个参数,返回相同类型的结果 对输入执行单一操作并返回相同类型 T apply(T t)
BinaryOperator<T> 接受两个相同类型参数,返回相同类型结果 将两个值组合成一个新值 T apply(T t1, T t2)

除此之外,JDK还提供了一些针对基本类型的接口,比如IntConsumer.accept(int value),目测是因为基础类型没法抽象成类T

多参数函数式接口

java.util.functional 中的接口是有限的,如果需要 3 个参数函数的接口怎么办?自己创建就可以了,如下:
// 创建处理 3 个参数的函数式接口

@FunctionalInterface
public interface TriFunction<T, U, V, R> {
    R apply(T t, U u, V v);
}

高阶函数(Higher-order Function)

在函数式编程中非常常见,它有以下特点:
接收一个或多个函数作为参数,返回一个函数作为结果

函数组合(Function Composition)

意为 “多个函数组合成新函数”。它通常是函数式 编程的基本组成部分。在 java.util.function 中常用的支持函数组合的方法,大致如下:

函数式接口 方法名 描述
Function<T, R> andThen 用于从左到右组合两个函数,即:h(x) = g(f(x))
Function<T, R> compose 用于从右到左组合两个函数,即:h(x) = f(g(x))
Consumer<T> andThen 用于从左到右组合两个消费者,按顺序执行两个消费者操作
Predicate<T> and 用于组合两个谓词函数,返回一个新的谓词函数,满足两个条件
Predicate<T> or 用于组合两个谓词函数,返回一个新的谓词函数,满足任一条件
Predicate<T> negate 用于对谓词函数取反,返回一个新的谓词函数,满足相反的条件
UnaryOperator<T> andThen 用于从左到右组合两个一元操作符,即:h(x) = g(f(x))
UnaryOperator<T> compose 用于从右到左组合两个一元操作符,即:h(x) = f(g(x))
BinaryOperator<T> andThen 用于从左到右组合两个二元操作符,即:h(x, y) = g(f(x, y))

知道有这么个东西,想用的时候甚至想问下能不能用的时候找GPT就好了

业务重构实践

事出起因是我们从下游拿到的数据格式非常原始,基本都是String,一些Object类型的字段其接口规格没有严格限制,我们需要校验并转型成内部可用的类。

特别地对于枚举类、布尔值、甚至基于业务条件有校验前更新需求的字段,校验的过程中就已经完成了转型,可以直接赋值到内部模型。

将校验和映射强行拆成两个独立的大步骤显然有重复工作,于是我发现了这个难的的应用函数式接口的机会,将单个字段的处理封装成一个统一的模版validateConvertAndMap,当然为了减少应用模版的代码量,还有其他重载的模版这里就不列举了

可以看到validateConvertAndMap把待校验字段的提取、检验及可选转型、映射这三个步骤用函数式接口抽象了出来,对于单个字段的处理整体实现了一步到位、不会有重复的工作。

public class Processor {

    public static <T, R> void validateConvertAndMap(
        String fieldName,
        Supplier<T> fieldSupplier,
        Function<T, R> validator, // with optional conversion
        Consumer<R> mapper,
        ProcessorContext context
    ) {
        T fieldValue = fieldSupplier.get();
        try {
            R validatedValue = (validator != null) ? validator.apply(fieldValue) : (R) fieldValue;
            if (mapper != null) {
                mapper.accept(validatedValue);
            }
        } catch (MyException e) {
            // wrap exception in context
        }
    }

    public static <E extends Enum<E>> Function<String, E> enumCastValidator(Class<E> klass, E defaultValue) {
        return (e) -> {
            if (StringUtils.isEmpty(e)) {
                return defaultValue;
            }
            for (final E enumConstant : klass.getEnumConstants()) {
                if (enumConstant.name().equals(e)) {
                    return enumConstant;
                }
            }
            throw new MyException("xxx")
        }
    }
}

特别地,对于validator函数还有高阶函数的玩法,比如enum的校验转型validator写成了二阶函数,通过传入enum类型和默认值返回一个针对特定enum类型的校验函数,进一步优化了代码重用

Reference: https://www.cnblogs.com/xiao2shiqi/p/17280618.html

1

评论区