目录

汪沫远的个人博客

一直爱着从痛苦荒芜中生出的喜悦

X

Java中如何优雅的进行含优先级的数据获取(链式调用)

Java中如何优雅的进行含优先级的数据获取(链式调用)

在实际的开发过程中,我们往往遇到这样的情形:

  • 优先从A获取数据
  • A中没有,则从B中取
  • B中也没有,则从C中取
  • 。。。。。。

你猜我为啥不往下写了,子子孙孙无穷匮也

预先准备

在展开讨论之前,我们先定义从各个地方获取数据的方法。当然,返回值不一定是字符串,在本例中,规定返回值为null时认为数据不合法,应该从下一个地方获取数据:

    private String getDataFromA() {
        // 这是我从A获取的数据,数据不合法,返回空
        return null;
    }

    private String getDataFromB() {
        // 这是我从B获取的数据,巧了,数据也不合法,返回空
        return null;  
    }

    private String getDataFromC() {
        return "这是我从C获取的数据";
    }

    private String getDataFromD() {
        return "这是我从D获取的数据";
    }

一般做法


在上述的情况下,我们通常的做法是(方式一):

        String dataFromA = getDataFromA();
        if (dataFromA != null) {
            System.out.println(dataFromA);
        } else {
            String dataFromB = getDataFromB();
            if (dataFromB != null) {
                System.out.println(dataFromB);
            } else {
                // 到这真写不下去了
            }
        }

更有经验的做法是(方式二):

		String dataFromA = getDataFromA();
        if (dataFromA != null) {
            System.out.println(dataFromA);
            return;
        }
      
        String dataFromB = getDataFromB();
        if (dataFromB != null) {
            System.out.println(dataFromB);
            return;
        }
      
        String dataFromC = getDataFromC();
        if (dataFromC != null) {
            System.out.println(dataFromC);
            return;
        }
      
        String dataFromD = getDataFromD();
        if (dataFromD != null) {
            System.out.println(dataFromD);
            return;
        }

可以明显看到,方式二看起来比方式一更加优雅,看起来更加整洁。但是问题来了,无论方式一二,大量的篇幅都在用来判断数据是否合规。更重要的是,这样的逻辑可能在很多地方都会写。那么有没有方法能简化一下,让代码看起来更简洁?并且能够推广到大部分类似的情形?

引入链式调用与泛型

熟悉OkHttp或者java的stream操作的一定对链式调用不陌生,以stream为例:

        List<String> list = new ArrayList<>(Arrays.asList("1", "123", "342314", "12313", "21312"));
        List<Integer> result = list.stream()
                .map(Integer::valueOf)
                .sorted(Integer::compare)
                .collect(Collectors.toList());
        System.out.println(result);
        // [1, 123, 342314, 12313, 21312]

可以看到,通过引入streamlambda、链式调用,我们轻松完成了:

字符串转数字->对数字排序->将结果整合为List

最重要的是,代码看起来简洁了不少


回到刚刚的话题,如何进行本文情形的优化?可以用链式调用的方法实现,为了方便,我直接定义静态内部类;当然也可以在一个单独的类中定义:

    public static class DataGetter<T> {
        private T result;
      
        public DataGetter<T> order(Supplier<T> supplier) {
            if (result != null) {
                // 已经获取到了正常的数据,不用再执行一次获取操作了
                // 这么写的原因是很多时候获取操作是很耗时间的,节约时间
                return this;
            }
          
            // 执行一次数据获取
            T t = supplier.get();
            // 这里我认为非法数据为null
            if (t != null) {
                result = t;
            }
          
            // 这里的作用是返回它本身,得以继续链式调用
            return this;
        }
      
        // 获取值
        public T get() {
            return result;
        }
    }

使用泛型的目的是为了让数据获取可以不仅仅局限与某一类数据。此时,要获取数据的方法可以直接优化为:

        String result = new DataGetter<String>()
                .order(() -> getDataFromA())
                .order(() -> getDataFromB())
                .order(() -> getDataFromC())
                .order(() -> getDataFromD())
                .get();

        System.out.println(result);
        // 这是我从C获取的数据

会按照order调用的顺序进行依次获取数据,直到获取合法的值,之后order调用则不会进行数据获取操作

优化与推广

加入默认值

有些时候,当所有数据来源均为非法,我们需要设置一个默认值:

    public static class DataGetter<T> {
        private T result;
        private T defaultResult;

        // 设置默认值
        public DataGetter<T> defaultResult(T defaultResult) {
            this.defaultResult = defaultResult;
            return this;
        }
      
        public DataGetter<T> order(Supplier<T> supplier) {
            if (result != null) {
                // 已经获取到了正常的数据,不用再执行一次获取操作了
                // 这么写的原因是很多时候获取操作是很耗时间的,节约时间
                return this;
            }

            // 执行一次数据获取
            T t = supplier.get();
            // 这里我认为非法数据为null
            if (t != null) {
                result = t;
            }

            // 这里的作用是返回它本身,得以继续链式调用
            return this;
        }

        // 获取值
        public T get() {
            // 获取值的时候判断一下result是否为空,非法且默认值不为空时返回默认值
            if (result == null && defaultResult == null) {
                return null;
            }
          
            if (result != null) {
                return result;
            }

            return defaultResult;
        }
    }

设置默认值可以为以下操作:

        String result = new DataGetter<String>()
                .defaultResult("这是默认值,所有获取数据均非法时返回此值")
                .order(() -> getDataFromA())
                .order(() -> getDataFromB())
                .get();

        System.out.println(result);
        // 这是默认值,所有获取数据均非法时返回此值

复杂的非法判断

很多时候数据非法不仅仅只是判断是否为null,往往需要更复杂的判断:
先修改getDataFromC()方法:

    private String getDataFromC() {
        // 这是我从C获取的数据,虽然数据是非法的,但它不为null,气不气?
        return "非法数据!";
    }

DataGetter做修改:

    public static class DataGetter<T> {
        private T result;
        private T defaultResult;
        private Predicate<T> predicate;

        // 传入的方法执行后如果为true,则认为合法
        public DataGetter<T> notIllegal(Predicate<T> predicate) {
            this.predicate = predicate;
            return this;
        }

        // 设置默认值
        public DataGetter<T> defaultResult(T defaultResult) {
            this.defaultResult = defaultResult;
            return this;
        }

        public DataGetter<T> order(Supplier<T> supplier) {
            if (result != null) {
                // 已经获取到了正常的数据,不用再执行一次获取操作了
                // 这么写的原因是很多时候获取操作是很耗时间的,节约时间
                return this;
            }

            // 执行一次数据获取
            T t = supplier.get();
            // 传入notIllegal,满足传入的条件才合法
            if (predicate != null) {
                if (predicate.test(t)) {
                    result = t;
                }
            }
            // 没有传入notIllegal,则依旧认为为null不合法
            else if (t != null) {
                result = t;
            }


            // 这里的作用是返回它本身,得以继续链式调用
            return this;
        }

        // 获取值
        public T get() {
            // 获取值的时候判断一下result是否为空,非法且默认值不为空时返回默认值
            if (result == null && defaultResult == null) {
                return null;
            }

            if (result != null) {
                return result;
            }

            return defaultResult;
        }
    }

此时数据获取时:

        String result = new DataGetter<String>()
                .defaultResult("这是默认值,所有获取数据均非法时返回此值")
                // 认为获取到的数据不为空且不等于"非法数据!"时合法
                .notIllegal(data -> data != null && !"非法数据!".equals(data))
                .order(() -> getDataFromA())
                .order(() -> getDataFromB())
                .order(() -> getDataFromC())
                .order(() -> getDataFromD())
                .get();

        System.out.println(result);
        // 这是我从D获取的数据

最终效果:加入适配器

当我们需要把得到的结果进行一次转化,从一类数据转化为另外一类数据时,继续修改DataGetter类:

    public static class DataGetter<T> {
        private T result;
        private T defaultResult;
        private Predicate<T> predicate;

        // 传入的方法执行后如果为true,则认为合法
        public DataGetter<T> notIllegal(Predicate<T> predicate) {
            this.predicate = predicate;
            return this;
        }

        // 设置默认值
        public DataGetter<T> defaultResult(T defaultResult) {
            this.defaultResult = defaultResult;
            return this;
        }

        public DataGetter<T> order(Supplier<T> supplier) {
            if (result != null) {
                // 已经获取到了正常的数据,不用再执行一次获取操作了
                // 这么写的原因是很多时候获取操作是很耗时间的,节约时间
                return this;
            }

            // 执行一次数据获取
            T t = supplier.get();
            // 传入notIllegal,满足传入的条件才合法
            if (predicate != null) {
                if (predicate.test(t)) {
                    result = t;
                }
            }
            // 没有传入notIllegal,则依旧认为为null不合法
            else if (t != null) {
                result = t;
            }


            // 这里的作用是返回它本身,得以继续链式调用
            return this;
        }

        // 获取值
        public T get() {
            // 获取值的时候判断一下result是否为空,非法且默认值不为空时返回默认值
            if (result == null && defaultResult == null) {
                return null;
            }

            if (result != null) {
                return result;
            }

            return defaultResult;
        }

        // 传入一个适配器,将一类数据转为另外一类数据
        public <R> R adapterGet(Function<T, R> function) {
            if (function == null) {
                return null;
            }

            return function.apply(get());
        }
    }

此时调用时:

		DataGetter<String> getter = new DataGetter<String>()
                .defaultResult("这是默认值,所有获取数据均非法时返回此值")
                // 认为获取到的数据不为空且不等于"非法数据!"时合法
                .notIllegal(data -> data != null && !"非法数据!".equals(data))
                .order(() -> getDataFromA())
                .order(() -> getDataFromB())
                .order(() -> getDataFromC())
                .order(() -> getDataFromD());

        // 字符串转化为字符list
        List<Character> chars = getter.adapterGet(data -> {
            List<Character> result = new ArrayList<>();
            for (int i = 0; i < data.length(); i++) {
                result.add(data.charAt(i));
            }
            return result;
        });
        System.out.println(chars);
        // [这, 是, 我, 从, D, 获, 取, 的, 数, 据]

        // 将字符串转为字符串,但是转化后的字符串为原本字符传去除第一个字符
        String result = getter.adapterGet(data -> data.substring(1));
        System.out.println(result);
        // 是我从D获取的数据

标题:Java中如何优雅的进行含优先级的数据获取(链式调用)
作者:汪沫远
地址:https://blog.wangzetong.online/articles/2024/08/24/1724495587003.html