案例分析

在软件工程中,一个众所周知的问题就是,不管你做什么,用户的需求肯定会变。比方说,有个应用程序是帮助农民了解自己的库存的。这位农民可能想有一个查找库存中所有绿色苹果的功能。但到了第二天,他可能会告诉你:“其实我还想找出所有重量超过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实战”书中借鉴的,写这篇博客也是为了记录下学习的过程,以后忘了可以翻来看看,写的不好敬请谅解,谢谢。

浅谈Java行为参数化和Lambda表达式相关推荐

  1. java 中的单元测试_浅谈Java 中的单元测试

    单元测试编写 Junit 单元测试框架 对于Java语言而言,其单元测试框架,有Junit和TestNG这两种, 下面是一个典型的JUnit测试类的结构 package com.example.dem ...

  2. java的byte php_java_浅谈java的byte数组的不同写法,(由于篇幅原因阐述的不够详 - phpStudy...

    浅谈java的byte数组的不同写法 (由于篇幅原因阐述的不够详细科学,不喜勿喷). 经常看到java中对byte数组的不同定义,粗略整理的一下: 一个字节(byte)=8位(bit),"b ...

  3. 浅谈 Java Printing

    浅谈 Java  Printing 其实怎么说呢?在写这篇博文之前,我对java printing 可以说是一无所知的.以至于我在敲文字时, 基本上是看着api文档翻译过来的.这虽然看起来非常的吃力, ...

  4. java接口与类相同不同_浅谈java的接口和C++虚类的相同和不同之处

    C++虚类相当于java中的抽象类,与接口的不同之处是: 1.一个子类只能继承一个抽象类(虚类),但能实现多个接口 2.一个抽象类可以有构造方法,接口没有构造方法 3.一个抽象类中的方法不一定是抽象方 ...

  5. java对象头_浅谈java对象结构 对象头 Markword

    概述 对象实例由对象头.实例数据组成,其中对象头包括markword和类型指针,如果是数组,还包括数组长度; | 类型 | 32位JVM | 64位JVM| | ------ ---- | ----- ...

  6. java执行jar中的main_浅谈java 执行jar包中的main方法

    浅谈java 执行jar包中的main方法 通过 OneJar 或 Maven 打包后 jar 文件,用命令: java -jar ****.jar 执行后总是运行指定的主方法,如果 jar 中有多个 ...

  7. 浅谈Java中的Set、List、Map的区别

    就学习经验,浅谈Java中的Set,List,Map的区别,对JAVA的集合的理解是想对于数组: 数组是大小固定的,并且同一个数组只能存放类型一样的数据(基本类型/引用类型),JAVA集合可以存储和操 ...

  8. Java 函数式编程和 lambda 表达式

    为什么要使用函数式编程 函数式编程更多时候是一种编程的思维方式,是种方法论.函数式与命令式编程的区别主要在于:函数式编程是告诉代码你要做什么,而命令式编程则是告诉代码要怎么做.说白了,函数式编程是基于 ...

  9. 浅谈Java网络编程之Socket (2)

    <浅谈Java网络编程之Socket (1)>中我们已经和大家说到客户端的网络编程,下面和大家分享的是服务器的实现代码. import java.net.*; import java.io ...

最新文章

  1. 《程序是怎样跑起来的》第二章
  2. 云服务器centos怎么还原系统还原,云服务器centos怎么还原系统还原
  3. 算力用多少买多少,竞享实例太香了
  4. MTK 驱动开发(54)---MTK-thermal.conf温度参数修改的方法
  5. iap php,PHP语言之华为应用内支付IAP验签
  6. elasticsearch映射相关字段定义,属性定义,及动态映射(marvel插件方式)mapping
  7. 修复VSS 2005
  8. secoclient在Mac下使用无法上网的解决办法
  9. 中值滤波(Median filtering)
  10. Tampermonkey油猴插件
  11. Premiere 音频视频基本设置
  12. linux系统新增一块SCSI硬盘并分区挂载到指定目录
  13. DbVisualizer常见问题解答(ddl标签不存在)
  14. java课程设计模板_《JAVA课程设计模板.doc
  15. 一年级古诗风语文知识心田花开汇总
  16. 中关村科技企业融资缺口700亿 商业银行垂涎
  17. 《微信小程序案例5》仿小米Lite小程序分类板块-两个纵向滚动区域独立互不影响
  18. 弘辽科技:淘宝新店提升销量可以吗?怎么提升关键词?
  19. 网上订餐系统 mysql 数据库设计_网上订餐系统的设计与实现
  20. iOS学习笔记22 推送通知

热门文章

  1. 网站漏洞挖掘测试服务
  2. JAVA SHA-1加密及DES加解密
  3. 中海达RTK(星移差分)使用指南
  4. 安装 Oracle 12c
  5. mpvue使用vuex基本步骤以及如何使用
  6. 页面置换之最近最久未使用置换算法
  7. VggNet 论文分析
  8. 面试指南,求职必看 ! 大学毕业生找工作必备指南!
  9. 荣耀畅玩4x android 6,华为荣耀4X EMUI4.0回退EMUI3.1教程 安卓6.0降级5.1方法
  10. 易语言 kernelbase.dll c0000005,【已解决】win7资源管理器停止工作,出错原因是kernelbase.dll...