`

通过行为参数化传递代码(Java)

阅读更多

行为参数化就是可以帮你处理频繁变更的需求的一种软件开发模式。

接下来,让我们来看看如何应对不断变化的需求。

 

1.初试牛刀:筛选绿苹果
一种常见的实现方式可能是这样:

public static List<Apple> filterGreenApples(List<Apple> inventory) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
        if ("green".equals(apple.getColor())) {
            result.add(apple);
        }
    }
    return result;
}

 

2.再展身手:把颜色作为参数
如果想要筛选红苹果,你该怎么做呢?简单的解决办法就是复制这个方法,把名字改成filterRedApples,然后更改if条件来匹配红苹果。
然而,如果想要筛选多种颜色:浅绿色、暗红色、黄色等,这种方法就应付不了了。

一个良好的原则是在编写类似的代码之后,尝试将其抽象化。

一种做法是给方法加一个参数,把颜色变成参数,这样就能灵活地适应变化了:

public static List<Apple> filterApplesByColor(List<Apple> inventory, String color) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
        if (apple.getColor().equals(color)) {
            result.add(apple);
        }
    }
    return result;
}

 

3.新需求:按重量筛选苹果
一种可能的实现方式,是另写一个按重筛选苹果方法:

public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
        if (apple.getWeight() > weight) {
            result.add(apple);
        }
    }
    return result;
}

优点
解决思路不错,不与筛选颜色的方法耦合,做到
一个方法只做一件事情,简单明了。

缺点
复制了大部分的代码来实现遍历库存,并对每个苹果应用筛选条件。
这有点儿令人失望,因为它
打破了DRY(Don't Repeat Yourself,不要重复自己)的软件工程原则

如果你想要改变筛选遍历方式来提升性能呢?那就得修改所有方法的实现,而不是只改一个。从工程工作量的角度来看,这代价太大了。

 

4.糟糕的实现方式:一个方法筛选多个属性
如果筛选的属性较多,由于前一种实现方式会产生大量的重复代码,于是另一种可能的实现方式如下:

public static List<Apple> filterApples(List<Apple> inventory, String color, int weight, boolean flag) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
        if ((flag && apple.getColor().equals(color))
                || (!flag && apple.getWeight() > weight)) {
            result.add(apple);
        }
    }
    return result;
}

通过一个方法来实现对多个属性的筛选,看似简化了代码,防止出现大量重复性代码。
但却存在以下缺点

  1. 代码复杂度明显增加,可阅读性也变差了。如果筛选的属性越多,这个缺点会越明显。

  2. 从使用者的角度来看,接口参数变得难以理解了。比如flag代表什么意思,什么时候传color或weight,这无形增加了使用者对接口理解的成本。

  3. 如果还有其他属性需要筛选,比如大小、形状等,那接口将会变得异常复杂。

  4. 对于这样复杂的一个接口,使用时可能还会传错参数

 

5.行为参数化:多种行为,一个参数
一种更高层次的方式,是将筛选的行为抽象化,将筛选的逻辑作为参数来传递。这样一方面可以复用遍历逻辑,另一方面可以应对多种多样的筛选需求

抽象出一个通用的筛选接口:
public interface ApplePredicate {
    boolean test(Apple apple);
}

按重量筛选:
public class AppleHeavyWeightPredicate implements ApplePredicate {
    public boolean test(Apple apple) {
        return apple.getWeight() > 150;
    }
}
按颜色筛选:
public class AppleGreenColorPredicate implements ApplePredicate {
    public boolean test(Apple apple) {
        return "green".equals(apple.getColor());
    }
}

利用ApplePredicate改过之后,filter方法看起来是这样的:

public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
        if (p.test(apple)) {
            result.add(apple);
        }
    }
    return result;
}

这有点类似于“策略设计模式”,先定义一族算法,把它们封装起来(称为“策略”),然后在运行时选择一个实现策略。
在这里,算法族就是ApplePredicate,不同的策略就是AppleHeavyWeightPredicate和AppleGreenColorPredicate。

 

行为参数化的好处
可以
把迭代要筛选的集合的逻辑与对集合中每个元素应用的行为区分开来。这样你可以重复使用同一个方法,给它不同的行为来达到不同的目的。(将不变与变化部分进行分离

 

6.匿名类
通过行为参数化,我们似乎已经找到了较为理想的实现方式了。
但是,
行为参数化仍然存在一个问题,就是对于每个行为的传递,必须通过实现一个封装该行为的类,最后实例化后再进行传递。

可不可以再简单一点呢?对于Java 8之前,答案是匿名类
对于匿名类,我们或多或少都会在项目中使用,想必大家应该都不会陌生了。例如GUI事件处理、Comparator排序实现、Runnable执行代码块等。

GUI事件处理
Button button = new Button("Send");
button.setOnAction(new EventHandler<ActionEvent>() {
    public void handle(ActionEvent event) {
        label.setText("Sent!!");
    }
});

Comparator排序
inventory.sort(new Comparator<Apple>() {
    public int compare(Apple a1, Apple a2) {
        return a1.getWeight().compareTo(a2.getWeight());
    }
});

Runnable执行代码块
Thread t = new Thread(new Runnable() {
    public void run{
        System.out.println("Hello world");
    }
});

通过创建一个用匿名类实现ApplePredicate的对象,重写筛选的例子:

List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
    public boolean test(Apple apple) {
        return "red".equals(apple.getColor());
    }
});

 

7.使用Lambda表达式
接下来,我们再来看看Java 8引入Lambda表达式的实现方式:

List<Apple> result = filterApples(inventory, (Apple apple) -> "red".equals(apple.getColor()));

不错,干净漂亮。一方面更好的陈述了问题本身,另一方面解决了代码的啰嗦问题

 

8.持续改进:将List类型抽象化
通过
类型泛型化,使筛选逻辑更加通用。

public interface Predicate<T{
    boolean test(T t);
}

public static <T> List<T> filter(List<T> list, Predicate<T> p) {
    List<T> result = new ArrayList<>();
    for (T e : list) {
        if (p.test(e)) {
            result.add(e);
        }
    }
    return result;
}

List<Apple> redApples = filter(inventory, (Apple apple) -> "red".equals(apple.getColor()));
List<Integer> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0);

怎么样,既灵活又简洁,还不赶紧使用Java 8亲测一把^_^。

 

小结

  • 行为参数化(类、匿名类、Lambda):灵活,值参数化:死板。

  • 行为参数化可让代码更好地适应不断变化的要求,减轻未来的工作量。

  • 将不变与变化部分解耦,既复用了代码,又保证了灵活性。

  • 没有最好只有更好,持续改进代码。

以上内容整理自《Java 8 实战》第2章,排版及内容略做调整,更易于渐进理解。

 

转载请注明来源:http://zhanjia.iteye.com/blog/2426409

 

 

个人公众号

二进制之路

 

 

 

 

 

 

0
0
分享到:
评论

相关推荐

    java8stream源码-java8__:在Java中练习“现代Java实战”示例的代码

    章:通过行为参数化传递代码 第 3 章:Lambda 表达式 第 4 章:使用流 第 5 章:使用流处理数据 第 6 章:使用流收集数据 第 7 章:并行数据处理和性能 第 8 章:重构、测试、调试 第 9 章:默认方法 第 10 章:使用...

    java8stream源码-Java8InAction:Java8上的示例代码和项目

    章:通过行为参数化传递代码 第 3 章:Lambda 表达式 第 4 章:使用流 第 5 章:使用流处理数据 第 6 章:使用流收集数据 第 7 章:并行数据处理和性能 第 8 章:重构、测试、调试 第 9 章:默认方法 第 10 章:使用...

    java8stream源码-java8_in_action:“java8inaction”研究的示例代码

    章:通过行为参数化传递代码 第 3 章:Lambda 表达式 第 4 章:使用流 第 5 章:使用流处理数据 第 6 章:使用流收集数据 第 7 章:并行数据处理和性能 第 8 章:重构、测试、调试 第 9 章:默认方法 第 10 章:使用...

    java8源码-java8:java8笔记和代码示例

    行为参数化 接口中的默认方法 Date Time Api Optional 类 Stream 和 Collection的区别 Stream 描述对数据的计算 Collection 存储和访问数据 行为参数化 让方法接受多种行为作为参数,并在内部使用,来完成不同的行为...

    Table_map_log_event内容详解.pdf

    本书的主要内容如下: Java 8实战目录第一部分 基础知识 第1章 为什么要关心Java 8 2 第2章 通过行为参数化传递代码 20 第3章 Lambda表达式 34 第二部分 函数式数据处理 第4章 引入流 68 第5章 使用流 82 ...

    java8stream源码-Java8:Java8

    章:通过行为参数化传递代码 第 3 章:Lambda 表达式 第 4 章:使用流 第 5 章:使用流处理数据 第 6 章:使用流收集数据 第 7 章:并行数据处理和性能 第 8 章:重构、测试、调试 第 9 章:默认方法 第 10 章:使用...

    java8stream源码-JavaTraining:Java培训

    章:通过行为参数化传递代码 第 3 章:Lambda 表达式 第 4 章:使用流 第 5 章:使用流处理数据 第 6 章:使用流收集数据 第 7 章:并行数据处理和性能 第 8 章:重构、测试、调试 第 9 章:默认方法 第 10 章:使用...

    java8stream源码-Java8source:Java8源代码

    章:通过行为参数化传递代码 第 3 章:Lambda 表达式 第 4 章:使用流 第 5 章:使用流处理数据 第 6 章:使用流收集数据 第 7 章:并行数据处理和性能 第 8 章:重构、测试、调试 第 9 章:默认方法 第 10 章:使用...

    java8-examples:Java8高级编程示例

    第 2 章:通过行为参数化传递代码 第 3 章:Lambda 表达式 第 4 章:使用流 第 5 章:使用流处理数据 第 6 章:使用流收集数据 第 7 章:并行数据处理和性能 第 8 章:重构、测试、调试 第 9 章:默认方法 第 10 章...

    action源码java-road.to.java8.master:该代码基于Java8inAction及其源代码

    第2章:通过行为参数化传递代码 第3章:Lambda表达式 第4章:使用流 第5章:使用流处理数据 第6章:使用流收集数据 第7章:并行数据处理和性能 第8章:重构,测试,调试 第9章:默认方法 第10章:使用Optional作为...

    java8stream源码-java8:Java8——函数式编程

    使用行为参数化传递代码 第 3 章 Lambda 表达式 第 4 章 介绍流 第 5 章 使用流 第 6 章使用流收集数据 第 7 章并行数据处理和性能 第 8 章重构、测试和调试 第 9 章默认方法 第 10 章使用 Optional 作为 null 的更...

    java8stream源码-java8:java8

    章:通过行为参数化传递代码 第 3 章:Lambda 表达式 第 4 章:使用流 第 5 章:使用流处理数据 第 6 章:使用流收集数据 第 7 章:并行数据处理和性能 第 8 章:重构、测试、调试 第 9 章:默认方法 第 10 章:使用...

    java8stream源码-modernjava:在操作代码中使用现代Java

    章:通过行为参数化传递代码 第 3 章:Lambda 表达式 第 4 章:使用流 第 5 章:使用流处理数据 第 6 章:使用流收集数据 第 7 章:并行数据处理和性能 第 8 章:重构、测试、调试 第 9 章:默认方法 第 10 章:使用...

    java8stream源码-git-flow-practise:git-flow-练习

    章:通过行为参数化传递代码 第 3 章:Lambda 表达式 第 4 章:使用流 第 5 章:使用流处理数据 第 6 章:使用流收集数据 第 7 章:并行数据处理和性能 第 8 章:重构、测试、调试 第 9 章:默认方法 第 10 章:使用...

    java8stream源码-java8InAction:java8InAction

    章:通过行为参数化传递代码 第 3 章:Lambda 表达式 第 4 章:使用流 第 5 章:使用流处理数据 第 6 章:使用流收集数据 第 7 章:并行数据处理和性能 第 8 章:重构、测试、调试 第 9 章:默认方法 第 10 章:使用...

    java8stream源码-lambdasinaction:java8在行动

    章:通过行为参数化传递代码 第 3 章:Lambda 表达式 第 4 章:使用流 第 5 章:使用流处理数据 第 6 章:使用流收集数据 第 7 章:并行数据处理和性能 第 8 章:重构、测试、调试 第 9 章:默认方法 第 10 章:使用...

    action源码java-Java8InAction-1:源代码

    第2章:通过行为参数化传递代码 第3章:Lambda表达式 第4章:使用流 第5章:使用流处理数据 第6章:使用流收集数据 第7章:并行数据处理和性能 第8章:重构,测试,调试 第9章:默认方法 第10章:使用Optional作为...

    java8stream源码-lambdasinaction:java8新特性

    章:通过行为参数化传递代码 第 3 章:Lambda 表达式 第 4 章:使用流 第 5 章:使用流处理数据 第 6 章:使用流收集数据 第 7 章:并行数据处理和性能 第 8 章:重构、测试、调试 第 9 章:默认方法 第 10 章:使用...

    Java设计模式(含代码)

    Command:将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可以取消的操作。 Composite:将对象组合成树形结构以表示“部分-整体”的层次结构。...

    java8stream源码-kimmisun:金米顺

    章:通过行为参数化传递代码 第 3 章:Lambda 表达式 第 4 章:使用流 第 5 章:使用流处理数据 第 6 章:使用流收集数据 第 7 章:并行数据处理和性能 第 8 章:重构、测试、调试 第 9 章:默认方法 第 10 章:使用...

Global site tag (gtag.js) - Google Analytics