浅谈Java行为参数化和Lambda表达式

案例分析

在软件工程中,一个众所周知的问题就是,不管你做什么,用户的需求肯定会变。比方说,有个应用程序是帮助农民了解自己的库存的。这位农民可能想有一个查找库存中所有绿色苹果的功能。但到了第二天,他可能会告诉你:“其实我还想找出所有重量超过150克的苹果。”又过了两天,农民又跑回来补充道:“要是我可以找出所有既是绿色,重量也超过150克的苹果,那就太棒了。”你要如何应对这样不断变化的需求?理想的状态下,应该把你的工作量降到最少。此外,类似的新功能实现起来还应该很简单,而且易于长期维护。

下面我们就一步一步的来实现这个农民伯伯的需求:

1、筛选出所有绿色的苹果:
我们首先想到应该是来遍历一个list,从中一个一个比较这些苹果的颜色,把是绿色的苹果放到新的list中,然后返回给农民,告诉他这些都是绿色的苹果,代码如下:

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

好,我们高兴的完成了农民想要的结果,交付给农民,然而,在你交付的那天,农民说他不想要绿色的苹果了,想要红色的苹果,然后你内心是不是有点小小的崩溃,但是客户是上帝,然后你只能默默的回去改了,改的时候想了一下,农民说不定后面还会改变苹果的颜色,然后你就想到了传个颜色的参数来修改这个方法,那么好,下面开始动工了。
2、筛选出任意颜色的苹果:

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

现在,只要像下面这样调用方法,农民朋友就会满意了:

List<Apple> greenApples = filterApplesByColor(inventory, "green");
List<Apple> redApples = filterApplesByColor(inventory, "red");
…

是不是觉得自己分牛逼了,不管农民想要啥颜色的苹果,都能以最小的修改来实现,可是农民伯伯就是要跟你作对呀,他说:“要是能区分轻的苹果和重的苹果就太好了。重的苹果一般是重量大于150克。”作为软件工程师,你早就想到农民可能会要改变重量,于是你写了下面的方法,用另一个参数来应对不同的重量:
3、筛选中较重的苹果:

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

好了,这下农民伯伯没话说了吧,可是有没发现,你已经进入了复制/粘贴的开发模式了,1、2、3这几段代码,大部分内容是相同的,只是修改了一个比较条件,是不是觉得很无聊,也很无奈,这时你可能会想到加入一个标识来区分颜色和重量。
4、根据flag区分颜色和重量的筛选:

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

你可以这么用(但真的很笨拙):

List<Apple> greenApples = filterApples(inventory, "green", 0, true);
List<Apple> heavyApples = filterApples(inventory, "", 150, false);
…

我的天哪,看起来糟糕透了,完全不能理解这个true/false是干嘛用的,其次万一农民伯伯要求你对苹果的不同属性做筛选,比如大小、形状、产地等,怎么办?而且,如果农民要求你组合属性,做更复杂的查询,比如绿色的重苹果,又该怎么办?

好了,废话也讲了一大堆了,下面就进入正题–行为参数化,介绍行为参数化之前还是先给上代码吧,代码如下:
5、使用行为参数化来实现农民的各种需求:
首先,让我们定义一个接口来对选择标准建模:

public interface ApplePredicate{
    boolean test (Apple apple);
}

现在你就可以用 ApplePredicate 的多个实现代表不同的选择标准了,比如:

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 对象,并将它们传递给 filterApples方法。免费的灵活性!比如,如果农民让你找出所有重量超过150克的红苹果,你只需要创建一个类来实现 ApplePredicate 就行了。你的代码现在足够灵活,可以应对任何涉及苹果属性的需求变更了:

public class AppleRedAndHeavyPredicate implements ApplePredicate{
    public boolean test(Apple apple){
        return "red".equals(apple.getColor()) && apple.getWeight() > 150;
    }
}
List<Apple> redAndHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate());

你已经做成了一件很酷的事: filterApples 方法的行为取决于你通过 ApplePredicate 对象封装了测试苹果的条件象传递的代码。换句话说,你把 filterApples 方法的行为参数化了!

经过上面的描述相信你已经大概知道什么是行为参数化了,但是,有没有发现什么不足的地方,是不是觉得还是很罗嗦,农民伯伯来一个新需求就需要实现一个类,而且这个类只实现了一个方法,真的很啰嗦啊,怎么办呢?java8之前是提供了一些解决方案的,比如:匿名类,关于匿名类我就不详细描述了,可去查找其它资料,下面只举一个例子来改善上面的代码:
6、使用匿名类实现农民伯伯的需求:

List<Apple> redApples = filterApples(inventory, new ApplePredicate() {//ApplePredicate是先前定义的接口
    public boolean test(Apple apple){
        return "red".equals(apple.getColor());
    }
});

确实是稍微简单了些哈,最起码不用重复的写实现类了,但另外的问题又出来了,可读性差啊,感觉有点绕有木有?好吧,那我们再优化一下好了,怎么优化?使用java8新特性中的 Lambda表达式,代码如下:
7、使用 Lambda表达式来实现农民伯伯的以上需求:

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

那不光要实现农民伯伯的需求,又引进了其它需求怎么办呢?比如:不是苹果了,变成了萝卜青菜了,这就需要对参数进行抽象化了,代码如下:

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之前是不可能做到的!

本篇主要是介绍Java的行为参数化,因此对Lambda没有进行深入的讲解。以上代码是我从“Java8实战”书中借鉴的,写这篇博客也是为了记录下学习的过程,以后忘了可以翻来看看,写的不好敬请谅解,谢谢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值