行为参数化就是可以帮助你处理频繁变更需求的一种软件开发模式。它意味着拿出一个代码块,把它准备好却不去执行它。这个代码块以后可以被你程序的其他部分调用,这意味着你可以推迟这块代码的执行。例如:你可以将代码块作为参数传递给另一个方法,稍后再去执行它。

应对不断变化的需求

1.第一次尝试:实现一个功能,从一个列表中筛选出绿色苹果的功能。

首先准备Apple实体类

public class Apple {
private Integer Id;
private String Color;
private Double Weight; //getter..setter..toString..
}

编写过滤出绿色苹果的功能

    public static List<Apple> filter(List<Apple> apples){
List<Apple> result = new ArrayList<>();
for(Apple apple : apples){
//筛选出绿色的苹果
if("green".equals(apple.getColor())){
result.add(apple);
}
}
return result;
}

测试数据

    public static void main(String[] args) {
List<Apple> apples = Arrays.asList(
new Apple(1,"green",18.0),
new Apple(2,"yellow",36d),
new Apple(3,"red",42d),
new Apple(4,"green",15d),
new Apple(5,"red",16d)
); List<Apple> greenApple = filter(apples);
System.out.println(greenApple); }

输出结果:

[Apple{Id=1, Color='green', Weight='18.0'}, Apple{Id=4, Color='green', Weight='15.0'}]

实现功能了,现在产品说我又想要筛选红色的苹果了,最简单的办法就是复制这个方法,把名字改成filterRedApples,然后更改if条件来匹配红苹果,然而产品想要筛选更多颜色的苹果,黄色、橘黄色、大酱色等,这种方法就不行了,将颜色作为参数

2.第二次尝试:把颜色作为参数

    //把颜色作为参数
public static List<Apple> filterApplesByColor(List<Apple> apples,String color){
List<Apple> result = new ArrayList<>();
for(Apple apple : apples){
if(color.equals(apple.getColor())){
result.add(apple);
}
}
return result;
}

现在只需要这样调用,产品就会满意了。

List<Apple> yellowApple = filterApplesByColor(apples,"yellow");
List<Apple> redApple = filterApplesByColor(apples,"red");

然后产品又跑过来说,要是能区分苹果大小就太好了,把大于32的分为大苹果,小于32的分为小苹果。

于是你下了下面的方法,又增加了重量的参数

    //根据重量来筛选苹果
public static List<Apple> filterApplesByWeight(List<Apple> apples,Double weight){
List<Apple> result = new ArrayList<>();
for(Apple apple : apples){
if(apple.getWeight() > weight){
result.add(apple);
}
}
return result;
}

你终于实现了产品的需求,但是请注意,你复制了大部分的代码来实现遍历苹果列表,并对每个苹果筛选条件。这有点令人失望,因为它打破了Don't Repeat Yourself(不要重复你自己)的软件工程原则。

3.第三次尝试:把你能想到的每个属性做筛选

你可以将颜色和重量结合为一个方法,称为filterColorOrWeight,然后增加一个参数来区分需要筛选哪个属性。

    //按颜色筛选或按重量筛选
public static List<Apple> filterColorOrWeight(List<Apple> apples, String color, Double weight, boolean flag) {
List<Apple> result = new ArrayList<>();
for (Apple apple : apples) {
if ((flag && color.equals(apple.getColor())) || (!flag && apple.getWeight() > weight)) {
result.add(apple);
}
}
return result;
}

然后你可以这样使用:

List<Apple> yellowApples = filterColorOrWeight(apples, "yellow",0d,true);
List<Apple> bigApples = filterColorOrWeight(apples, "",32d,false);

这个解决方案再差不过了,首先客户端代码看上去烂透了,true和false是什么意思?此外,这个解决方案还是不能很好的区应对变化的需求,如果产品又让你对苹果的其他不同的属性做筛选,比如大小、形状、产地等,又该怎么办?或者要求你组合属性,作更复杂的查询,比如绿色的大苹果,又该怎么办?

行为参数化

  你在上一节中看到了,你需要一种比添加很多参数更好的方法来应对变化的需求。 一种解决方案是对你选择的标准建模:比如根据Apple的某些属性(是否是绿色,是否大于32)来返回一个boolean值,我们把它称之为谓词。首先我们来定义一个借口来对选择标准建模。

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

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

public class AppleGreenColorPredicate implements ApplePredicate {
//绿苹果谓词
public boolean test(Apple apple) {
return "green".equals(apple.getColor());
}
}
public class AppleHeavyWeightPredicate implements ApplePredicate {
//大苹果谓词
public boolean test(Apple apple) {
return apple.getWeight() > 32;
}
}

现在,你可以把这些标准看做filter方法的不同行为。你刚做的这些和“策略设计模式”相关,它让你定义一组算法,把他们封装起来(成为“策略”),然后在运行时选择一个算法。在这里,ApplePredicate就是算法组,不同的策略就是AppleHeavyWeightPredicate和AppleGreenColorPredicate。

你需要filterApples方法接受ApplePredicate对象,对Apple做条件测试。这就是行为参数化:让方法接受多种行为(或战略)作为参数,并在内部使用,来完成不同的行为。

 

4.第四次尝试:根据抽象条件筛选

    //根据抽象条件筛选
public static List<Apple> filterApples(List<Apple> apples,ApplePredicate p){
List<Apple> result = new ArrayList<>();
for(Apple apple : apples){
if(p.test(apple)){
result.add(apple);
}
}
return result;
}

使用的时候这样:

List<Apple> greenApple = filterApples(apples,new AppleGreenColorPredicate());
List<Apple> bigApple = filterApples(apples,new AppleHeavyWeightPredicate());

1.传递代码/行为

到这里可以小小的庆祝一下了,这段代码比我们第一次尝试的时候灵活多了,读起来、用起来也更容易!现在你可以创建不同的ApplePredicate对象,并将他们传递给filterApples方法。比如现在产品让你找出所有重量超过80克的红苹果,你只需创建一个类来实现ApplePredicate就行了,你的代码现在足够灵活,可以应对任何设计苹果属性的需求变更了:

public class AppleRedAndBigPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return "red".equals(apple.getColor()) && apple.getWeight() > 80;
}
}

传递给filterApples方法,无需修改filterApples方法的内部实现:

List<Apple> redBigApple = filterApples(apples,new AppleRedAndBigPredicate());

现在你做了一件很酷的事:filterApples方法的行为取决于你通过ApplePredicate对象传递的代码。换句话说,你把filterApples方法行为参数化了!

在上一个例子中,唯一重要的代码是test方法的实现,正式它定义了filterApples方法的新行为。由于filterApples方法只能接受对象,所以你必须把代码包裹在ApplePredicate对象里。这种做法类似于在内联“传递代码”,因为你是通过一个实现了test方法的对象来传递布尔表达式的。

2.多种行为,一个参数

  行为参数化的好处在于你可以把遍历集合的逻辑和 对集合中每个元素的判断逻辑 区分开来。这样就可以重复使用同一个方法,给它不同的行为来达到不同的目的。

行为参数化练习

  编写printApple方法,实现一个功能,以多种方式根据苹果生成一个String输出(例如,可以让printApple方法只打印每个苹果的颜色,或者让它打印什么颜色的大(小)苹果)

创建AppleFormater接口

public interface AppleFormater {
String accept(Apple a);
}

创建AppleWeightFormater、AppleColorFormater 来实现接口

public class AppleWeightFormater implements AppleFormater {
@Override
public String accept(Apple a) {
return "这是一个" + a.getColor() + "的" + (a.getWeight() > 32 ? "大" : "小") + "苹果";
}
}
public class AppleColorFormater implements AppleFormater {
@Override
public String accept(Apple a) {
return "一个" + a.getColor() + "的苹果";
}
}

然后创建printApple方法,给它传递不同的formater对象

    public static void printApple(List<Apple> apples,AppleFormater af) {
for (Apple apple : apples) {
String output = af.accept(apple);
System.out.println(output);
}
}

测试:

printApple(apples, new AppleWeightFormater());
printApple(apples, new AppleColorFormater());

现在,你可以把行为抽象出来了,让你的代码更加适应需求的变化,但是这个过程很啰嗦,因为你需要声明很多只要实例化一次的类。

匿名类

使用匿名类来对付这些只需要使用一次的类。

比如说使用匿名类来新增加一个打印样式为:哇塞,这是一个大苹果啊?

printApple(apples, new AppleFormater() {
public String accept(Apple a) {
return "哇塞,这是一个" + (a.getWeight() > 32 ? "大" : "小") + "苹果啊?";
}
});

使用匿名类虽然解决了为一个接口声明好几个实体类的啰嗦问题,但它仍然不能令人满意。

使用Lambda表达式

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

printApple(apples, (Apple a) -> "哇塞,这是一个" + (a.getWeight() > 32 ? "大" : "小") + "苹果啊?");

这样看起来是不是更清爽多了?更像是在描述问题本身!关于lambda将会在下节介绍。

将List类型抽象化

目前filterApples方法还只适用于Apple,可以将List类型抽象化,让它支持香蕉、橘子、Integer或是String的列表上!

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> greenApple = filter(apples,(Apple apple) -> "green".equals(apple.getColor()));
System.out.println(greenApple); List<Integer> evenNumbers = filter(Arrays.asList(1,2,3,4,5,6,10),(Integer i) -> i%2 ==0);
System.out.println(evenNumbers);

帅不帅?你现在在灵活性和间接性之间找到了最佳的平衡点,这在java 8之前是不可能做到的!

真实的例子

  你现在已经看到,行为参数化是一个很有用的模式,它能够轻松的适应不断变化的需求。这种模式可以把一个行为(一段代码)封装起来,并通过传递和使用穿件的行为(例如对Apple的不同谓词)将方法的行为参数化。

1.使用Comparator来排序

  对集合排序是一个常见的任务,比如,产品过来说想按照苹果的重量进行排序。在java 8中,List自带了一个sort方法(也可以使用Collections.sort)。sort的行为可以用java.util.Comparator对象来参数化,它的接口如下:

package java.util;

@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}

因此,你可以随时创建Comparator的实现,用sort方法来排序,使用匿名类按照重量升序排序:

apples.sort(new Comparator<Apple>() {

        @Override
public int compare(Apple o1, Apple o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
});

使用lambda如下:

apples.sort((Apple a1,Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));

2.用Runnable执行代码块

  线程就像是轻量级的进程:它们自己执行一个代码块。在Java离可以使用Runnable接口表示一个要执行的代码块。

package java.lang;

@FunctionalInterface
public interface Runnable { public abstract void run();
}

使用匿名类创建执行不同行为的线程:

Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t111");
}
});

使用Lambda:

Thread t2 = new Thread(() -> System.out.println("t2222"));

小结:

  1.行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力。

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

  3.传递代码,就是将新行为作为参数传递给方法,在java 8 之前这实现起来很啰嗦。为接口声明许多只用一次的实体类而造成的啰嗦代码,在java 8 之前可以使用匿名类来减少。

  4.java API 包含很多可以用不同行为进行参数化的方法、包括排序、线程等。

Java 8 (1) 行为参数化的更多相关文章

  1. 行为驱动:Cucumber + Java - 实现数据的参数化

    1.什么是参数化 实际设计测试用例过程中,我们经常会用等价类.边界值这样的方法,针对一个功能进行测试数据上的测试,比如一个输入框,正向数据.逆向数据,非法输入等等 2.Cucumber的数据驱动 同上 ...

  2. 《Java 8 in Action》Chapter 2:通过行为参数化传递代码

    你将了解行为参数化,这是Java 8非常依赖的一种软件开发模式,也是引入 Lambda表达式的主要原因.行为参数化就是可以帮助你处理频繁变更的需求的一种软件开发模式.一言以蔽之,它意味 着拿出一个代码 ...

  3. Java第10次实验(数据库)

    参考资料 数据结构实验参考文件 MySql操作视频与数据库相关jar文件请参考QQ群文件. 第1次实验 1. MySQL数据库基本操作 完整演示一遍登录.打开数据库.建表.插入 常见错误:语句后未跟; ...

  4. Java第11次实验(数据库)

    参考资料 数据结构实验参考文件 数据库初始化文件 MySQL操作视频 数据库相关jar文件请参考QQ群文件. 第1次实验 1. MySQL数据库基本操作 完整演示一遍登录.打开数据库.建表.插入 常见 ...

  5. Java自动化环境搭建笔记(1)

    Java自动化环境搭建笔记(1) 自动化测试 先搭建java接口测试的环境: 使用mvn命令构建项目 测试集通过testNG.xml组织并运行 测试数据解耦,通过Excel等文件提供 基础依赖 创建m ...

  6. 实现 Castor 数据绑定--转

    第 1 部分: 安装和设置 Castor 数据绑定风靡一时 在 XML 新闻组.邮件列表和网站的讨论论坛中(在 参考资料 中可以找到这些内容的链接),最常见的一个主题就是数据绑定.Java 和 XML ...

  7. Jenkins pipeline:pipeline 语法详解

    jenkins  pipeline 总体介绍 pipeline 是一套运行于jenkins上的工作流框架,将原本独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂流程编排与可视化. ...

  8. Jenkins pipeline 语法详解

    原文地址http://www.cnblogs.com/fengjian2016/p/8227532.html pipeline 是一套运行于jenkins上的工作流框架,将原本独立运行于单个或者多个节 ...

  9. Javac的命令(-Xlint)

    在OptionName类中的枚举定义如下: XLINT("-Xlint"), XLINT_CUSTOM("-Xlint:"), -Xlint     Enabl ...

随机推荐

  1. SQL SERVER代理作业删除失败问题

    在SQL Server 2005上遇到了先删除已运行维护计划后,再删除代理中由其产生的作业时,提示删除失败.   DELETE 语句与 REFERENCE 约束"FK_subplan_job ...

  2. codevs——1017 乘积最大

    1017 乘积最大 2000年NOIP全国联赛普及组NOIP全国联赛提高组  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解       题目描述 Desc ...

  3. JSP国际化设置

    以下内容引用自http://wiki.jikexueyuan.com/project/jsp/internationalization.html: 国际化(i18n):这意味着可以使网站根据访问者的语 ...

  4. Ionic3 填坑记录 - java.lang.RuntimeException: java.lang.RuntimeException: com.android.builder.dexing.DexArchiveMergerException: Unable to merge dex

    1  错误:Unable to merge dex 执行打包命令时 ionic cordova build android --prod 报如下错误 2 原因 重复引用了同一个包 如上图所示, com ...

  5. openstack setup demo Compute service

    本文包含以下部分 Compute service overview Install and configure controller node Prerequisites Install and co ...

  6. ajax 请求 get请求成功,post 404 not found

    今天在做express的一个ajax请求的时候发现get请求成功,post 就一直是 404 not found, 研究了半天 才发现是express 的请求方法做了限制,之前一直都是用的 app.g ...

  7. CSS (二)解析CSS盒子

    话说.一写博客还有些莫名的兴奋感-- 这几天一直挤时间忙于赶牛腩视频,迟到的CSS盒子.请谅解. CSS盒子,一開始听起来还有点高大上的赶脚. 后来了解之后,发现事实上非常easy理解.从功能上讲非常 ...

  8. javascript闭包具体解释

    今天我们从内存结构上来解说下 javascript中的闭包概念. 闭包:是指有权訪问另外一个函数作用域中的变量的函数. 创建闭包的常见方式就是在一个函数内部创建另外一个函数. 在javascript中 ...

  9. 玩转单元測试之WireMock -- Web服务模拟器

    WireMock 是一个灵活的库用于 Web 服务測试,和其它測试工具不同的是.WireMock 创建一个实际的 HTTPserver来执行你的 Web 服务以方便測试. 它支持 HTTP 响应存根. ...

  10. linux文件系统的权限简单介绍

    linux系统下,文件的权限是这样表示的: - --- --- --- 一共用10位的二进制进行表示,其中 位置 0 :  - :文件        d :目录剩下的9个位置:位置1-3 当前用户(应 ...