美文网首页Java 杂谈程序员
《Java 8 in Action》Chapter 8:重构、测

《Java 8 in Action》Chapter 8:重构、测

作者: 后端小哥 | 来源:发表于2019-03-23 08:08 被阅读6次

    我们会介绍几种方法,帮助你重构代码,以适配使用Lambda表达式,让你的代码具备更好的可读性和灵活性。除此之外,我们还会讨论目前比较流行的几种面向对象的设计模式,
    包括策略模式、模板方法模式、观察者模式、责任链模式,以及工厂模式,在结合Lambda表达式之后变得更简洁的情况。最后,我们会介绍如何测试和调试使用Lambda表达式和Stream API的代码。

    1. 为改善可读性和灵活性重构代码

    1.1 改善代码的可读性

    Java 8的新特性也可以帮助提升代码的可读性:

    • 使用Java 8,你可以减少冗长的代码,让代码更易于理解
    • 通过方法引用和Stream API,你的代码会变得更直观

    利用Lambda表达式、方法引用以及Stream改善程序代码的可读性:

    • 重构代码,用Lambda表达式取代匿名类
    • 用方法引用重构Lambda表达式
    • 用Stream API重构命令式的数据处理

    1.2 从匿名内部类到Lambda表达式的转换

    将实现单一抽象方法的匿名类转换为Lambda表达式

    // 传统的方式,使用匿名类
    Runnable r1 = new Runnable(){
        public void run(){
            System.out.println("Hello");
        }
    }
    // 新的方式,使用Lambda表达式
    Runnable r2 = () -> System.out.println("Hello");
    

    匿名 类和Lambda表达式中的this和super的含义是不同的。在匿名类中,this代表的是类自身,但是在Lambda中,它代表的是包含类。其次,匿名类可以屏蔽包含类的变量,而Lambda表达式不能(它们会导致编译错误),如下面这段代码:

    int a = 10;
    Runnable r1 = () -> {
        int a = 2;                    // 编译错误
        System.out.println(a);
    };
    Runnable r2 = new Runnable() {
        public void run() {
            int a = 2;                // 正常
            System.out.println(a);
        }
    }
    

    在涉及重􏰴的上下文里,将匿名类转换为Lambda表达式可能导致最终的代码更加晦涩。实际上,匿名类的类型是在初始化时确定的,而Lambda的类型取决于它的上下文。通过下面这个例子,我们可以了解问题是如何发生的。我们假设你用与Runnable同样的签名声明了一个函数接口,我们称之为Task:

    interface Task{
        public void execute();
    }
    public static void doSomething(Runnable r){ r.run(); }
    public static void doSomething(Task a){ a.execute(); }
    doSomething(new Task() {
            public void execute() {
                System.out.println("Danger danger!!");
            }
    });
    // doSomething(Runnable) 和 doSomething(Task) 都匹配该类型
    doSomething(() -> System.out.println("Danger danger!!"));
    // 使用显式的类型转换来解决这种模棱两可的情况
    doSomething((Task)() -> System.out.println("Danger danger!!"));
    

    目前大多数的集成开发环境,比如NetBeans和IntelliJ都支持这种重构,它们能自动地帮你检查,避免发生这些问题。

    1.3 从Lambda表达式到方法引用的转换

    Map<CaloricLevel, List<Dish>> dishesByCaloricLevel =
            menu.stream()
                .collect(
                    groupingBy(dish -> {
                         if (dish.getCalories() <= 400) return CaloricLevel.DIET;
                        else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
                        else return CaloricLevel.FAT;
    }));
    

    将Lambda表达式的内容抽取到一个单独的方法中,将其作为参数传递给groupingBy方法。变换之后,代码变得更加简洁,程序的意图也更加清晰了。

    Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream().collect(groupingBy(Dish::getCaloricLevel));
    

    1.4 从命令式的数据处理切换到Stream

    我们建议你将所有使用迭代器这种数据处理模式处理集合的代码都转换成Stream API的方式。为什么呢?
    Stream API能更清晰地表达数据处理管道的意图。除此之外,通过短路和延迟载入以及利用第7章介绍的现代计算机的多核架构,我们可以对Stream进行优化。

    // 命令式版本
    List<String> dishNames = new ArrayList<>();
        for(Dish dish: menu){
            if(dish.getCalories() > 300){
                dishNames.add(dish.getName());
        }
    }
    // 使用Stream API
    menu.parallelStream()
            .filter(d -> d.getCalories() > 300)
            .map(Dish::getName)
            .collect(toList());
    

    1.5 增加代码的灵活性

    没有函数式接口就无法使用Lambda表达式,因此代码中需要引入函数式接口。引入函数式接口的两种通用模式:

    • 有条件的延迟执行
    • 环绕执行

    2. 使用Lambda重构面向对象的设计模式

    使用Lambda表达式后,很多现存的略显臃肿的面向对象设计模式能够用更精简的方式实现了。这一节中,我们会针对五个设计模式展开讨论,它们分别是:

    • 策略模式
    • 模板方法
    • 观察者模式
    • 责任链模式
    • 工厂模式

    2.1 策略模式

    策略模式代表了解决一类算法的通用解决方案,你可以在运行时选择使用哪种方案。策略模式包含三部分内容,如图所示。

    • 一个代表某个算法的接口(它是策略模式的接口)。
    • 一个或多个该接口的具体实现,它们代表了算法的多种实现(比如,实体类ConcreteStrategyA或者ConcreteStrategyB)。
    • 一个或多个使用策略对象的客户。
    公众号二维码.jpg

    相关文章

      网友评论

        本文标题:《Java 8 in Action》Chapter 8:重构、测

        本文链接:https://www.haomeiwen.com/subject/oovbmqtx.html