背景:
根据《java8实战》把第二章简单概括一下。
在软件工程中,一个最重要的问题是,用户的需求会一直变化,如何应对不断变化的需求,并且把工作量降到最低是需要考虑的,而行为参数化就是一个处理频繁变更需求的软件开发模式。
在我看来,行为参数化,是拿出一块代码段,准备好却不去执行,这个代码块会被其他部分调用,意味着可以推迟这个代码的执行。
下面用在库存中筛选苹果的案例来说明。
第一版:筛选绿苹果
public static List<Apple> filterGreenApples(List<Apple> inventory) {
List<Apple> result = new ArrayList<>();
for(Apple app: inventory) {
if("green".equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
上面的代码可以就是筛选绿色苹果,但是如果现在需求变了,需要筛选红色苹果,那么第一反应是在上面的函数入参中,加入颜色条件来匹配。
第二版:颜色作为参数
public static List<Apple> filterGreenApples(List<Apple> inventory, String color) {
List<Apple> result = new ArrayList<>();
for(Apple app: inventory) {
if(apple.getColor().equals(color)) {
result.add(apple);
}
}
return result;
}
List<Apple> greenApples = filterAppleByColor(Inventory, "green");
List<Apple> greenApples = filterAppleByColor(Inventory, "red");
这样可以得到最终答案,但是又出现了一个问题,现在需求变成需要能区分轻苹果和重苹果.
于是把上面代码拷贝一份又写了个针对重量的
第三版:重量作为参数
public static List<Apple> filterGreenApples(List<Apple> inventory, int weight) {
List<Apple> result = new ArrayList<>();
for(Apple app: inventory) {
if(apple.getWeight() > weight) {
result.add(apple);
}
}
return result;
}
这样虽然可以,但是打破了DRY(Don't Repeat Yourself, 不要重复自己)软件工程原则,当然不重复情况下,还可以加上一个标志位来判断是对哪个条件进行查询,如下
第四版:颜色和重量统一查询函数
public static List<Apple> filterGreenApples(List<Apple> inventory, String color,
int weight, boolean flag) {
List<Apple> result = new ArrayList<>();
for(Apple app: inventory) {
if((flag && apple.getColor().equals(color)) ||
(!flag && apple.getWeight() > weight)){
result.add(apple);
}
}
return result;
}
这样的代码可读性很差,并且标志位现在只是颜色和重量,如果再加查询条件,比如产地,形状等,就无法满足需求了.
行为参数化传递代码
考虑将选择的标准进行建模,比如绿色的吗,重量超过150克吗,来返回一个boolean值,这种返回boolean值函数称为”谓词“,定义一个接口来选择标准建模:
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());
}
}
把不同filter方法的不同行为封装起来,称为“策略”,然后再运行时选择一个算法,这个算法族就是applePredicate。
第五版:applePredicate改造后
public static List<Apple> filterApples(List<Apple> inventory,
ApplePredicate p) {
List<Apple> result = new ArrayList<>();
for(Apple app: inventory) {
if(p.test(apple)){
result.add(apple);
}
}
return result;
}
这样使用灵活多了,假设选择需求变了,组合条件,既要红苹果,又要重量超过150克,那么就再增加一个类实现ApplePredicate就行
public class AppleRedAndHeavyPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return "red".equals(apple.getColor()) && apple.getWeight() > 150;
}
}
List<Apple> redAndHeavyApple = filterApples(inventory, new AppleRedAndHeavyPredicate());
但是这样还不完美,因为现在filterApples入参都需要一个new一个过滤条件相关的对象,并且实现test方法,为了在进一步减少代码,还可以用匿名类和lambda表达式
第六版:匿名类
List<Apple> redAndHeavyApple = filterApples(inventory, new AppleRedAndHeavyPredicate() {
public boolean test(Apple apple) {
return "red".equals(apple.getColor());
}
});
匿名类看起来减少了类的实现代码,但是调用时候还是塞了很多代码。那么就到了最后一个
第七版:Lambda表达式
List<Apple> redAndHeavyApple =
filterApples(inventory, (Apple apple) -> "red".equals(apple.getColor()));
这样看起来就干净多了。现在filterApples只是作用于苹果的筛选,我们甚至更进一步,造一个通用filter,用泛型来替代Apple,如下:
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);
}
}
}
这样就可以把filter方法用在香蕉,桔子,等等上。这边的Predicate<T>是一个函数式的接口,为了缩短篇幅,会在下个博客讲