一等公民:函数

      ScalaGroovy 等语言的实践已经证明,让方法等概念作为一等公民可以扩充程序员的工具库,从而让编程变得更容易。因此,Java8的设计者决定允许方法作为值,让编程更轻松。

行为参数化

      在软件开发过程中,甲方爸爸的需求变化是频繁的。我们需要如何应对这样不断变化的需求,将我们的工作量降到最少。此外,类似的新功能实现起来还应该很简单,而且易于长期维护。
      行为参数化就是可以帮助我们处理频繁变更的需求的一种软件开发模式。这意味着你可以将代码块作为参数传递给另一个方法,稍后再去执行它。这样,这个方法的行为就基于那块代码被参数化了。


应对不断变化的需求

筛选绿苹果

      农民伯伯现在需要在一堆苹果中筛选出绿苹果,第一个解决方案如下:

private List<Apple> filterGreenApple(List<Apple> apples){
    ArrayList<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        //仅仅筛选绿苹果
        if( "green".equals(apple.getColor())){
            result.add(apple);
        }
    }
    return result;
}

      第一个方案解决了问题。但是现在农民伯伯想要筛选红苹果了,最简单的方法就是再实现一个 filterRedApple 方法,改变筛选条件即可。问题来了,如果农民伯伯还需要按照其他颜色进行筛选该肿么办?不可能一一实现对应方法吧~

根据颜色筛选

      你一定能想到我们将颜色作为一个参数传递进方法中不久解决了。

private List<Apple> filterAppleByColor(List<Apple> apples,String color){
    ArrayList<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        //按照传递过来的color筛选苹果
        if( color.equals(apple.getColor())){
            result.add(apple);
        }
    }
    return result;
}

      买过苹果的都知道苹果是分大小的,如75mm,85mm,90mm等。农民伯伯现在想要根据果子大小进行筛选。很简单,CV代码,修改成如下形式:

private List<Apple> filterAppleBySize(List<Apple> apples,int size){
    ArrayList<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        //按照传递过来的size筛选苹果
        if( apple.getSize() > size){
            result.add(apple);
        }
    }
    return result;
}

      似乎我们很好的解决了农民伯伯的需求,可是我们复制了大部分的代码来实现 List 的遍历。打破了 DRY(Don't Repeat Yourself) 的软件工程原则。

多属性筛选

      你又想到了一个解决之道,把所有属性都放在一起进行筛选。

private List<Apple> filterApple(List<Apple> apples,String color,Integer size){
    ArrayList<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        //将所有属性放在一起进行筛选
        if( color != null && apple.getColor().equals(color) || 
            size != null && apple.getSize() > size){
            result.add(apple);
        }
    }
    return result;
}

      可是此方法依旧不能很好地应对需求的变化。如果这位农民伯伯要求你对苹果的不同属性做筛选,比如重量、产地等,该如何实现?或者做一些复杂查询:红色的洛川苹果,又该如何?你可能需要实现无数个 filterApple 方法了。

行为参数化

      在上文中我们已经知道需要一种比传递很多参数更好的方法来应对变化的需求。让我们来看看更高层次的抽象。一种可能的解决方案就是对你的选择标准建模:你考虑的是苹果,需要根据 Apple 的某些熟悉来返回一个 boolean 值。我们把他称为谓词(即一个返回布尔值的函数)。让我们定义一个接口来对选择标准建模:

interface ApplePredicate{
    boolean test(Apple apple);    
}

      现在我们可以根据不同的筛选条件实现不同的谓词了:

//根据大小进行筛选
class AppleSizePredicateImpl implements ApplePredicate{
    @Override
    public boolean test(Apple apple) {
        return apple.getSize() > 75;
    }
}
//根据颜色进行筛选
class AppleColorPredicateImpl implements ApplePredicate{
    @Override
    public boolean test(Apple apple) {
        return "green".equals(apple.getColor());
    }
}

使用谓词筛选

private List<Apple> filterApple(List<Apple> apples,ApplePredicate predicate){
    ArrayList<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        if(predicate.test(apple)){
            result.add(apple);
        }
    }
    return result;
}

      目前这段代码比之前的版本灵活多了,我们可以创建不同的谓词传递给 filterApple 方法。

一个参数,多种行为

      通过行为参数化你可以把待筛选的集合的逻辑与对集合中的每个元素应用的具体行为区分开来。如此你可以重复使用同一个方法给它不同的行为来达到不同的目的。
筛选策略.png

简化参数

      目前,当要把新的行为传递给 filterApples 方法的时候,我们不得不声明好几个实现 ApplePredicate 接口的类,然后实例化好几个只会使用一次的 ApplePredicate 对象。真麻烦~,亟待解决。

使用匿名类

      匿名类和你熟悉的Java局部类(块中定义的类)差不多,但匿名类没有名字。它允许你同时声明并实例化一个类。换句话说,它允许你随用随建。下面代码展示了如何通过一个用匿名类实现 ApplePredicate 的对象重写筛选苹果的例子:

List<Apple> apples = filterApple(list, new ApplePredicate() {
    @Override
    public boolean test(Apple apple) {
        return "green".equals(apple.getColor());
    }
});

Lambda表达式

      上面的代码在Java8中可以用Lambda表达式重写为下面的样子:

List<Apple> apples = filterApple(list, apple -> "green".equals(apple.getColor()));

有关Lambda表达式的介绍在后续文章中查看

抽象List

      目前 filterApple 方法还只适用于 Apple 。我们还可以将List类型抽象化,从而超越眼前要处理的问题:

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

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

      现在我们可以把 filter 方法用在任意列表上了。

List<Apple> list = new ArrayList<>();
List<Apple> apples = filter(list, apple -> "green".equals(apple.getColor()));
        
List<Integer> list2 = new ArrayList<>();
List<Integer> integers = filter(list2, i -> i % 2 == 0);
Last modification:December 30th, 2020 at 08:12 pm
如果觉得我的文章对你有用,请随意赞赏