通过行为参数化传递代码
不管你做什么,用户的需求肯定会变。
之前
有个果农对自己的果园里的苹果采摘下来了,他想要对自己的苹果进行一个过滤,查找出所有颜色为绿色的苹果。
我们可以很轻易的编写出一个简单的程序进行实现。
public static List<Apple> filterGreenApples(List<Apple> inventory) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if ("green".equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
当需求更改,需要筛选为红色的苹果时,我们可能会想到复制这个方法,改个方法名filterRedApples,如果后续需要橙色、暗红色等。这个显而易见不太够了,我们需要进行一层复用。(并没有对参数等进行判空操作!)
public static List<Apple> filterApplesByColor(List<Apple> inventory, String color) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (color.equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
但是,筛选条件变成重量,又要怎么操作?难道新编写个方法filterApplesByWeight?显然不太行。你可能会把属性作为参数进行方法的封装,比如:
public static List<Apple> filterApples(List<Apple> inventory,
String color, int weight, boolean flag) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if ((flag && color.equals(apple.getColor()))
|| (!flag && apple.getWeight() > weight)) {
result.add(apple);
}
}
return result;
}
不提代码看起来很丑陋,后续如果条件更加复杂,那这个代码无论是写还是读,都是一个巨大的灾难!
之后
我们将之前的思路继续进行抽象,我们针对的过滤操作,无论是基于颜色、重量都是苹果的,因此我们可以针对苹果的某些属性进行封装,返回一个boolean,这称之为Predicate(谓词)。
public interface ApplePredicate {
boolean test(Apple apple);
}
我们可以通过继承ApplePredicate来实现对不同的苹果进行筛选的操作了。
public class AppleGreenColorPredicate implements ApplePredicate{
@Override
public boolean test(Apple apple) {
return "green".equals(apple.getColor());
}
}
public class AppleHeavyWeightPredicate implements ApplePredicate{
@Override
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
这些操作和策略设计模式相关。
怎么利用ApplePredicate的不同实现呢?你需要filterApples方法接受ApplePredicate对象,对Apple做条件测试。这就是行为参数化:让方法接受多种行为(或战略)作为参数,并在内部使用,来完成不同的行为。
public static List<Apple> filterApples(List<Apple> inventory,
ApplePredicate predicate) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (predicate.test(apple)) {
result.add(apple);
}
}
return result;
}
至此,filterApples方法的行为取决于你通过ApplePredicate对象传递的代码。换句话说,我们把filterApples方法的行为参数化了。目前我们的filterApples方法仅接收了一个对象作为入参,后续我们可以通过更加简洁的方式完成这个操作。
上述的代码缺点在于类过于冗余,我们可能仅仅使用AppleHeavyWeightPredicate一次,但是必须要进行一次类的继承与实例化。
JAVA为我们提供了匿名函数,可以让这些代码稍微优雅一些。
List<Apple> apples = filterApples(inventory, new ApplePredicate() {
@Override
public boolean test(Apple apple) {
return "green".equals(apple.getColor());
}
});
但是匿名函数还是不够简洁,当我们需要通过重量进行筛选时。
List<Apple> apples = filterApples(inventory, new ApplePredicate() {
@Override
public boolean test(Apple apple) {
return apple.getWeight()>150;
}
});
甚至某些时候,匿名函数可能会让人感到费解。
JAVA8出现了Lambda,我们可以使用Lambda重写代码成:
List<Apple> apples = filterApples(inventory,
(Apple apple) -> "green".equals(apple.getColor()));
到现在,代码已经变得非常优雅了,但我们从ApplePredicate这个接口的命名来看,我们就知道,代码的抽象及复用度不够。它目前仅仅适用于Apple,我们可以对它进行再抽象。
public interface Predicate<T> {
boolean test(T t);
}
我们通过这个接口,把filter方法运用到任何我们想要运用到的地方了!
行为参数化与值参数化的区别。值参数化过于死板,无论是从复用方面还是优美程度,都比不上行为参数化,行为参数化可以轻松适应不断变化的需求。
多种行为,一个参数!