基础概念
在软件工程中,一个众所周知的问题是,不管你做什么,用户的需求肯定会变。行为参数化就是可以帮助你处理频繁变更的需求的一种软件开发模式。一言以蔽之,它意味着拿出一个代码块,把它准备好却不去执行它,这个代码块以后可以被你程序的其它部分调用,这意味着你可以推迟这块代码的执行。
代码的不断演化
最初的样子:
public static List filterGreenApples(List inventory) {
List result = new ArrayList();
for(Apple apple: inventory){
if( "green".equals(apple.getColor() ) {
result.add(apple);
}
}
return result;
}
这段代码的作用不言而喻,筛选出绿色的苹果集,一旦颜色有所变更,就要重复的写一段类似的代码,在需求颜色相对较多的时候明显不切实际这样写的话,一个良好的原则是在编写类似的代码之后,尝试将其抽象化。
把颜色作为参数之后:
public static List filterApplesByColor(List inventory, String color) {
List result = new ArrayList();
for (Apple apple: inventory){
if ( apple.getColor().equals(color) ) {
result.add(apple);
}
}
return result;
}
现在,只要像下面这样调用方法,农民朋友就会满意了:
List greenApples = filterApplesByColor(inventory, "green");
List redApples = filterApplesByColor(inventory, "red");
取一种颜色的时候,只需要传对应衍射参数就可以了,如果要取两种或者两种以上的颜色时,再将集合取一个并集就可以了。但很明显的是,苹果的判断属性可能不止颜色这一个,例如重量,产地等。
对你能想到的每个属性做筛选:
public static List filterApples(List inventory, String color,int weight, int flag) {
List result = new ArrayList();
for (Apple apple: inventory){
if ( (flag == 1 && apple.getColor().equals(color)) || (flag == 2 && apple.getWeight() > weight)){
result.add(apple);
}
}
return result;
}
你可以这么用(但真的很笨拙):
List greenApples = filterApples(inventory, "green", 0, 1);
List heavyApples = filterApples(inventory, "", 150, 2);
去最终结果就是集合之间的处理了,此处不再阐述。这个解决方案再差不过了。首先,客户端代码看上去糟透了。 1 和 2 是什么意思?此外,这个解决方案还是不能很好地应对变化的需求。如果这位农民要求你对苹果的不同属性做筛选,比如大小、形状、产地等,又怎么办?而且,如果农民要求你组合属性,做更复杂的查询,比如绿色的重苹果,又该怎么办?你会有好多个重复的 filter 方法,或一个巨大的非常复杂的方法。
行为化参数:
接口
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());
}
}
你可以把这些标准看做筛选的不同标准,这些和“策略设计模式”相关,它让你定义一族算法,把它们封装起来(称为“策略”),然后在运行时选择一个算法。这就是行为参数化,让方法接受多种行为(或策略)作为参数,并在内部使用,来完成不同的行为。它的调用代码如下:
public static List filterApples(List inventory,ApplePredicate p){
List result = new ArrayList<>();
for(Apple apple: inventory){
if(p.test(apple)){
result.add(apple);
}
}
return result;
}
比如,如果农民让你找出所有重量超过150克的红苹果,你只需要创建一个类来实现 ApplePredicate 就行了。 filterApples 方法的行为取决于你通过 ApplePredicate 对象传递的代码。
对付啰嗦
我们都知道,人们都不愿意用那些很麻烦的功能或概念。目前,当要把新的行为传递给filterApples 方法的时候,你不得不声明好几个实现ApplePredicate 接口的类,然后实例化好几个只会提到一次的 ApplePredicate 对象。
使用匿名类:
List redApples = filterApples(inventory, new ApplePredicate() {
public boolean test(Apple apple){
return "red".equals(apple.getColor());
}
});
此时,根据筛选条件的不同,在匿名类中的实现也就不同。但是匿名类有两个明显的缺陷:1.占用了较多的空间;2.很多程序员觉得它用起来让人费解,也就是不好理解的意思。整体来说,啰 嗦就不好;它让人不愿意使用语言的某种功能,因为编写和维护 啰 嗦的代码需要很长时间,而且代码也不易读。好的代码应该是一目了然的。即使匿名类处理在某种程度上改善了为一个接口声明好几个实体类的 啰 嗦问题,但它仍不能令人满意。
在理想的情况下,我们想鼓励程序员使用行为参数化模式,因为正如你在前面看到的,它让代码更能适应需求的变化。在后面会详细介绍的,使用 Lambda 表达式,以下是使用示例:
List result =filterApples(inventory, (Apple apple) -> "red".equals(apple.getColor()));
filterApples 方法还只适用于 Apple 。你还可以将 List 类型抽象化,从而超越你眼前要处理的问题:
public interface Predicate{
boolean test(T t);
}
public static List filter(List list, Predicate p){
List result = new ArrayList<>();
for(T e: list){
if(p.test(e)){
result.add(e);
}
}
return result;
}
现在你可以把 filter 方法用在香蕉、桔子、 Integer 或是 String 的列表上了,下面是一些真实使用的例子:
1.用 Comparator 来排序
匿名类时的代码:
inventory.sort(new Comparator() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
Lambda表达式:
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
2.用Runnable执行代码块
匿名类时的代码:
Thread t = new Thread(new Runnable() {
public void run(){
System.out.println("Hello world");
}
});
Lambda表达式:
Thread t = new Thread(() -> System.out.println("Hello world"));
小结
- 行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力。
- 行为参数化可让代码更好地适应不断变化的要求,减轻未来的工作量。
- 传递代码,就是将新行为作为参数传递给方法。但在Java 8之前这实现起来很啰嗦。为接口声明许多只用一次的实体类而造成的啰嗦代码,在Java 8之前可以用匿名类来减少。
- Java API包含很多可以用不同行为进行参数化的方法,包括排序、线程和GUI处理。