2.行为参数的演变过程
行为参数化是软件开发模式,可以处理频繁变更的需求。它让你把一个代码块准备好但不执行,以后可以被其他部分调用,也可以作为参数传递给另一个方法,推迟执行。这样,方法的行为就基于参数化的代码块。
2.1 应对不断变化的需求
例如:现在需要一个方法直接可以找出绿色的水果。
//获取绿色的水果
public List<Fruit> filterGreenFruit(List<Fruit> fruits) {
List<Fruit> greenFruits = new ArrayList<>();
for (Fruit fruit : fruits) {
if ("green".equals(fruit.getColor())) {
greenFruits.add(fruit);
}
}
return greenFruits;
}
方法直接显示的展示筛选水果的颜色,若是想要筛选其他颜色,将其相同的行为抽象化。
第二个解决方案:把颜色作为参数
public List<Fruit> filterGreenFruit(List<Fruit> fruits,String color) {
List<Fruit> greenFruits = new ArrayList<>();
for (Fruit fruit : fruits) {
if (color.equals(fruit.getColor())) {
greenFruits.add(fruit);
}
}
return greenFruits;
}
把颜色作为参数,这样可以灵活应对不同颜色的筛选。
这时若是想要筛选水果的轻重,需要怎么处理呢?如下
public List<Fruit> filterWeightFruit(List<Fruit> fruits,int weight) {
List<Fruit> greenFruits = new ArrayList<>();
for (Fruit fruit : fruits) {
if (fruit.getWeight()>weight) {
greenFruits.add(fruit);
}
}
return greenFruits;
}
解决方法看似没什么问题,但是复制了大部分的代码来实现对重量的筛选,因为这打破了DRY(Don’t Repeat Yourself,不要重复自己)的软件工程原则。如果你想要改变筛选遍历方式来提升性能呢?那就得修改所有方法的实现,而不是只改一个。从工程工作量的角度来看,这代价太大了。
需要把颜色和重量放到一个方法里,并且添加一个标识,如第三个解决方案
第三个解决方案:对想到的每个属性做筛选
public List<Fruit> filterFruit(List<Fruit> fruits,int weight,String color,boolean flag) {
List<Fruit> greenFruits = new ArrayList<>();
for (Fruit fruit : fruits) {
//颜色和重量同时满足
if((!flag&&fruit.getColor().equals(color))||(flag&&fruit.getWeight()>weight)) {
greenFruits.add(fruit);
}
}
return greenFruits;
}
这个解决方案挺糟糕的,从使用端无法看出flag的 true和false 是什么意思,若是想要绿色的大的水果将无法实现。下面将用行为参数化灵活实现此功能。
2.2 行为参数化
前面我们添加多个参数来解决不断变化的需求,可以更高层次的抽象一个方法。一种可能的解决方案是对你的选择标准建模:你需要什么属性就根据某些属性实现test方法。代码如下
public interface ApplePredicate {
boolean test(Fruit fruit);
}
现在你就可以用ApplePredicate的多个实现代表不同的选择标准了,比如
//选择重的水果
public class FruitHeavyWeightPredicate implements ApplePredicate{
public boolean test(Apple apple){
return apple.getweight()>150;
}
}
//选择绿色的水果
public class FruitHeavyColorPredicate implements ApplePredicate{
public boolean test(Apple apple){
return apple.getColor.equals("green");
}
}
有了上面的两种实现,filterApples方法更加简单易懂,那就是把ApplePredicate 对象当作filterFruit的一个参数,需要不同行为时,传递不同的实现对象,这就是行为参数化:让方法接受多种行为(或战略)作为参数,并在内部使用,来完成不同的行为。
第四次尝试:根据抽象条件筛选
public List<Fruit> filterFruit(List<Fruit> fruits,ApplePredicate ap) {
List<Fruit> greenFruits = new ArrayList<>();
for (Fruit fruit : fruits) {
//直接用接口中的方法
if (ap.test(fruit)) {
greenFruits.add(fruit);
}
}
return greenFruits;
}
1.传递代码/行为 --实际也是Java的接口:实际就是行为抽象。
这段代码比之前尝试的时候灵活多了,读起来、用起来也更容易!现在你可以创建不同的ApplePredicate对象,并将它们传递给filterApples方法。例如 找出大于150g的红色水果,代码如下:
public class RedAndHeavyPredicate implements ApplePredicate {
@Override
public boolean test(Fruit fruit)
{
return "red".equals(fruit.getColor()) && fruit.getWeight() > 150;
}
}
List<Fruit> fruits = new ArrayList<>();
...省略添加数据步骤
List<Fruit> fruitList = filterFruit(fruits, new RedAndHeavyPredicate());
2.多种行为,一个参数
行为参数化的好处在于你可以把迭代要筛选的集合的逻辑与对集合中每个元素应用的行为区分开来。这样你可以重复使用同一个方法,给它不同的行为来达到不同的目的。
3 对付啰嗦
人们都不愿意用那些很麻烦的功能或概念。目前,当要把新的行为传递给filterApples方法的时候,你不得不声明好几个实现ApplePredicate接口的类,然后实例化好几个只会提到一次的ApplePredicate对象。可以尝试下匿名类:匿名类和Java内部类差不多,但匿名类没有类名称。它允许声明并实例化一个类(随用随创建)。
第五次尝试:匿名类
class RedPredicate implements ApplePredicate{
@Override
public boolean test(Fruit fruit)
{
return "red".equals(fruit.getColor()) && fruit.getWeight() > 150;
}
}
//调用
List<Fruit> redApples = filterFruit(fruits, new RedPredicate(){
public boolean test(Fruit fruit) {
return "red".equals(fruit.getColor());
}
});
第六次尝试:使用 Lambda 表达式
第五次尝试的代码在Java8可以写成如下代码:
List<Fruit> redFruit = filterFruit(fruits,(Fruit fruit)->"red".equals(fruit.getColor()))
2.3 例子
Java API中的很多方法都可以用不同的行为来参数化。这些方法往往与匿名类一起使用。
2.3.1用 Comparator 来排序
对集合进行排序是一个常见的编程任务。比如相对库存中的苹果做排序,在Java 8中,List自带了一个sort方法(你也可以使用Collections.sort)。sort的行为可以用java.util.Comparator对象来参数化,它的接口如下:
// java.util.Comparator public interface Comparator
{ public int compare(T o1, T o2); }
可以随时创建Comparator的实现,用sort方法表现出不同的行为。如下
inventory.sort(
new Comparator() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
}
);
java 8 的lambda表达式的写法如下
inventory.sort( (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
2.3.2用 Runnable 执行代码块
线程就像是轻量级的进程:它们自己执行一个代码块。在Java里,你可以使用Runnable接口表示一个要执行的代码块。
// java.lang.Runnable
public interface Runnable{
public void run();
}
//使用内部类创建不同行为的线程
Thread t = new Thread(
new Runnable() {
public void run(){
System.out.println("Hello world");
}
}
);
//Java 8写法
Thread t = new Thread(() -> System.out.println("Hello world"));
总结
-
行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力。
-
行为参数化可让代码更好地适应不断变化的要求,减轻未来的工作量。
-
传递代码,就是将新行为作为参数传递给方法。但在Java 8之前这实现起来很啰嗦。为接口声明许多只用一次的实体类而造成的啰嗦代码,在Java 8之前可以用匿名类来减少。
-
Java API包含很多可以用不同行为进行参数化的方法,包括排序、线程和GUI处理。