行为参数化是一个很重要的概念。它代表着我们可以事先准备好可以实现不同功能的代码块,当需要时,将这个代码块作为参数传递给某个方法。通过行为参数化可以帮助我们应对需求不断变化的代码。下面将会通过很多代码解释行为参数化。
将代码作为参数传递
例如,你要实现一个在很多人中筛选符合条件的功能。这些人都具有名字(name),年龄(age),性别(sex )这三个属性。当要求按性别筛选人时,你可能会这么实现这个功能:
public List<People> filterPeople(List<People> peopleList,String sex) {
List<People> result = new ArrayList<>();
for (People people : peopleList) {
if (people.getSex().equals(sex)) {
result.add(people);
}
}
return result;
}
当需求变化时,要求筛选大于指定年龄的人,于是,你又实现了一个新功能:
public List<People> filterPeople(List<People> peopleList, Integer age) {
List<People> result = new ArrayList<>();
for (People people : peopleList) {
if (people.getAge() >= age) {
result.add(people);
}
}
return result;
}
但事情没有结束,如果需求一个同时筛选性别和年龄的方法,你可能要再添加一个新的方法:
public List<People> filterPeople(List<People> peopleList, Integer age, String sex) {
List<People> result = new ArrayList<>();
for (People people : peopleList) {
if (people.getAge() >= age && people.getSex().equals(sex)) {
result.add(people);
}
}
return result;
}
这看起来好像很不错,但是又觉得很麻烦,因为你复制了很多重复的代码。这个时候,部分有经验的工程师可能会想到使用“策略设计模式”来应对需求不断变化的功能。定义一个接口,对筛选标准建模。
/**
* 筛选标准建模
*/
public interface FilterPeople {
boolean test(People people);
}
通过实现这个接口,将不同的策略传给筛选方法,达到根据不同要求筛选人的要求。
/**
* 根据不同策略筛选人功能
* @param peopleList 被筛选的人
* @param filterPeople 筛选策略
* @return 筛选结果
*/
public List<People> filterPeople(List<People> peopleList, FilterPeople filterPeople) {
List<People> result = new ArrayList<>();
for (People people : peopleList) {
if (filterPeople.test(people)) {
result.add(people);
}
}
return result;
}
当要求筛选性别为男性时:
public class FilterPeopleBySex implements FilterPeople {
@Override
public boolean test(People people) {
return "男".equals(people.getName());
}
}
当要求筛选年龄大于20时:
public class FilterPeopleByAge implements FilterPeople {
@Override
public boolean test(People people) {
return people.getAge() >= 20;
}
}
在需要的地方调用筛选方法:
//查询男性
List<People> man = filterPeople(peopleList, new FilterPeopleBySex());
//查询大于20岁的人
List<People> result = filterPeople(peopleList, new FilterPeopleByAge());
正如我们前面展示的,行为参数化能让我们把迭代的代码和不需要迭代的代码分开。这样我们可以重复使用同一个方法,然后给他不同的行为来达到不同的目的。跟之前的代码相比,现在只需要改变传入的策略就可以实现不同的功能。现在,filterPeople方法的功能取决于FilterPeople对象传递的代码(test方法中的代码)。换句话说,你把filterPeople方法的行为参数化了。但是,每次更换策略都要创建新的FilterPeople实现类,这真的很麻烦。我们需要改进一下上面的代码。我们想到可以使用匿名类,这样就可以不用创建新的类。
//查询大于20岁的人
List<People> result = filterPeople(peopleList, new FilterPeople() {
@Override
public boolean test(People people) {
return people.getAge() >= 20;
}
});
//查询男性
List<People> man = filterPeople(peopleList, new FilterPeople() {
@Override
public boolean test(People people) {
return "男".equals(people.getName());
}
});
//查询男性
List<People> man = filterPeople(peopleList, (People people)-> "男".equals(people.getName()));
//查询大于20岁的人
List<People> result = filterPeople(peopleList, (People people)-> people.getAge() >= 20);
Java API中很多方法都可以使用行为参数化,这些方法往往和匿名类一起使用,下面使用Comparator和Runnable进行演示。
例如对集合进行排序,在Java8 中,List自带了一个sort方法,通过传入不同的Comparator的实现来完成不通的排序:
//通过年龄排序
peopleList.sort(new Comparator<People>() {
@Override
public int compare(People p1, People p2) {
return p1.getAge().compareTo(p2.getAge());
}
});
//通过名称排序
peopleList.sort(new Comparator<People>() {
@Override
public int compare(People p1, People p2) {
return p1.getName().compareTo(p2.getName());
}
});
这样子我们就可以简洁的根据需求编写排序代码。
对于Runnable,我们可以通过实现不同的run方法,执行不同的行为:
//创建一个打印1的线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(1);
}
});
这样就不需要创建新的类,但是发现还是有一些重复的代码。而且你是通过传入不同FilterPeople对象来实实现,这有点类似于内联“代码传递”。在JDK1.8中,加入了Lambda表达式,可以将上面的代码简化为下面代码,这个跟前面的代码是完全等效的:
//查询男性
List<People> man = filterPeople(peopleList, new FilterPeopleBySex());
//查询大于20岁的人
List<People> result = filterPeople(peopleList, new FilterPeopleByAge());
//按年龄排序
peopleList.sort((p1, p2) -> p1.getAge().compareTo(p2.getAge()));
//按姓名排序
peopleList.sort((p1, p2) -> p1.getName().compareTo(p2.getName()));
//创建线程
Thread thread = new Thread(() -> System.out.println(1));
你会发现代码一下子就简洁了很多。现在只提出Lambda表达式,将会在下一篇详细的介绍Lambda的使用方法。
更多与JDK1.8相关的文章请看:Java JDK1.8 核心特性详解----(总目录篇)