行为参数化:就是一个方法接收多个不同的行为参数,并在内部使用他们,完成不同的行为能力。行为参数化可以更好的适应不断变化的需求,减少实际开发工作量。
现在有一些需求,苹果农场的农名将苹果采摘下来放在农场的仓库,需要从中筛选出所有绿色的苹果。
Apple类
public class Apple { private int weight = 0; private String color = ""; public Apple(int weight, String color){ this.weight = weight; this.color = color; } public Integer getWeight() { return weight; } public void setWeight(Integer weight) { this.weight = weight; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public String toString() { return "Apple{" + "color='" + color + '\'' + ", weight=" + weight + '}'; } }第一个解决方法
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,然后更改if条件来匹配红色苹果,但是如果要筛选出更多的颜色,黄色,青色等,这种方法可能就应付不了。一个良好的原则是在编写类似的代码之后,尝试将其抽象化。
第二个解决方法
给方法加一个颜色的参数,方法就变成下面这样:
public static List<Apple> filterApplesByColor(List<Apple> inventory, String color) { List<Apple> result = new ArrayList<Apple>(); for (Apple apple: inventory){ if ( apple.getColor().equals(color) ) { result.add(apple); } } return result; }现在只要在筛选的时候传入颜色就可以了。
但是时候农名又有了新的需求,需要区分轻的苹果和重的苹果,重的苹果大于150克
于是我们添加另一个参数来区分重的和轻的苹果
public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight) { List<Apple> result = new ArrayList<Apple>(); For (Apple apple: inventory){ if ( apple.getWeight() > weight ){ result.add(apple); } } return result; }解决方案看起来不错,只需复制更改少许的代码。但是这个还是有点令人失望,因为它违反了DRY((Don’t Repeat Yourself,不要重复自己)的软件规则,如果想改变筛选遍历来提升性能,那么修改的代码量将是是巨大的。
我们可以将颜色和重量结合未一个方法,称未filter,另外我们还需要一种方式来区分筛选属性,我们可以加上标记来区分对颜色和重量的查询。
第三种解决方法
public static List<Apple> filterApples(List<Apple> inventory, String color, int weight, boolean flag) { List<Apple> result = new ArrayList<Apple>(); for (Apple apple: inventory){ if ( (flag && apple.getColor().equals(color)) || (!flag && apple.getWeight() > weight) ){ result.add(apple); } } return result; }可以这样调用
List<Apple> greenApples = filterApples(inventory, "green", 0, true); List<Apple> heavyApples = filterApples(inventory, "", 150, false);这样解决很简单但是很糟糕,因为并不能很好的解决需求的变化,假如还要要求对形状、大小等进行区分等方法将会十分复杂,可读性也会很差。
上面已经看到,我们需要一种比添加很多参数更好的方法来应对变化需求,让我来看看更高层次的的抽象吧。因为要根据属性进行筛选,所以我们对选择进行标准化建模,根据Apple的属性来返回一个boolean值,我们把它称为谓词(即一个返回boolean值的函数)。
标准建模:
public interface ApplePredicate{ boolean test (Apple apple); }现在我们就可以ApplePredicate的多个实现代表不同的选择标准了,例如:
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()); } }上面的设计类似与策略设计模式。
下面我们只需要将ApplePredicate对象传入filterApples方法,对Apple做条件测试。这就是行为参数化,让方法接收多种行为作为参数,并在内存使用,来完成不同的行为。
第四种解决方式:抽象条件筛选
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p){ List<Apple> result = new ArrayList<>(); for(Apple apple: inventory){ if(p.test(apple)){ result.add(apple); } } return result; } List<Apple> redAndHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate());现在我们代码是不是已经足够灵活了,可以应对各种属性需求。其实这样已经很好了,但是我们还需思考思考,其实本质上,filterApples在内部进行条件判断的时候,我们使用的是不是仅仅是它内部的代码片段进行判断,即谓语的代码块,我们可不可以直接使用代码块呢?那样不是更好?就像下面
我们将行为参数化,直接传入方法内。这样我们也不用再定义那么多类了,而且代码的可读性也提高了。
行为参数化的好处在于我们可以把迭代要筛选的集合的逻辑与对集合中每个元素应用的行为区分开来,这样我们可以重复使用一个方法,达到不同行为实现不同的目的。
而在java8中为我们提供了行为参数化的方式,lambda表达式,极大的简化了我们代码的书写,可读性。
第五种解决方式
List<Apple> result = filterApples(inventory, (Apple apple) -> "red".equals(apple.getColor()));是不是豁然开朗?