本篇博客的主要内容
明白什么是行为参数化,介绍几种行为参数化的方式,为下一节学习Lambda打下基础。
什么事行为参数化
在软件工程中,一个众所周知的问题就是,不管你做什么,用户的需求肯定会变。比方说, 有个应用程序是帮助农民了解自己的库存的。这位农民可能想有一个查找库存中所有绿色苹果的 功能。但到了第二天,他可能会告诉你:“其实我还想找出所有重量超过150克的苹果。”又过了 两天,农民又跑回来补充道:“要是我可以找出所有既是绿色,重量也超过150克的苹果,那就太 棒了。”你要如何应对这样不断变化的需求?理想的状态下,应该把你的工作量降到最少。此外, 类似的新功能实现起来还应该很简单,而且易于长期维护。
行为参数化就是可以帮助你处理频繁变更的需求的一种软件开发模式。一言以蔽之,它意味着拿出一个代码块,把它准备好却不去执行它。这个代码块以后可以被你程序的其他部分调用, 这意味着你可以推迟这块代码的执行。例如,你可以将代码块作为参数传递给另一个方法,稍后 再去执行它。这样,这个方法的行为就基于那块代码被参数化了。例如,如果你要处理一个集合,可能会写一个方法:可以对列表中的每个元素做“某件事”可以在列表处理完后做“另一件事” 遇到错误时可以做“另外一件事”
行为参数化说的就是这个。打个比方吧:你的室友知道怎么开车去超市,再开回家。于是你可 以告诉他去买一些东西,比如面包、奶酪、葡萄酒什么的。这相当于调用一个goAndBuy方法,把 购物单作为参数。然而,有一天你在上班,你需要他去做一件他从来没有做过的事情:从邮局取一 个包裹。现在你就需要传递给他一系列指示了:去邮局,使用单号,和工作人员说明情况,取走包 裹。你可以把这些指示用电子邮件发给他,当他收到之后就可以按照指示行事了。你现在做的事情 就更高级一些了,相当于一个方法:go,它可以接受不同的新行为作为参数,然后去执行。
java 8之前的行为参数化种类
通过多个函数进行行为参数化
public class FilteringApples { public static void main(String[] args) { List< Apple > inventory = Arrays.asList( new Apple(80,"green"), new Apple(155,"green"), new Apple(120,"red")); List< Apple > greenApples = filterGreenApples(inventory); System.out.println(greenApples); List< Apple > heavyApples = filterHeavyApples(inventory); System.out.println(greenApples); } 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; } public static List< Apple > filterHeavyApples(List<Apple> inventory){ List< Apple > result = new ArrayList< >(); for (Apple apple:inventory){ if(apple.getWeight() > 150) result.add(apple); } return result; } } class Apple{ private int weight = 0; private String color = ""; public Apple(int weight, String color) { this.weight = weight; this.color = color; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } @Override public String toString() { return "Apple{" + "weight=" + weight + ", color='" + color + '\'' + '}'; } }
此种解决方案是不错的,但是有一点需要注意的是。你复制了大部分来吗来实现遍历Apple,并对每个苹果应用筛选条件。这是很不好的,因为我们打破了不要重复自己代码的工程原则。另外,如果如果又来一个筛选条件,你又必须去写一个函数来实现这个筛选条件。另外,如果我们改变遍历的条件,那么就需要修改所有的筛选函数,这代价也有点大。
通过接口进行行为参数化
public class FilteringPrintApples { public static void main(String[] args) { List< Apple > inventory = Arrays.asList(new Apple(80,"green"), new Apple(155,"green"), new Apple(120,"red")); prettyPrintApple(inventory,new AppleHeavyApples()); prettyPrintApple(inventory,new AppleSimpleFormatter()); } public static void prettyPrintApple(List< Apple> inventory, AppleFormatter formatter) { for (Apple apple : inventory) { String output = formatter.accept(apple); System.out.println(output); } } } //得到需要的apple接口 interface AppleFormatter { String accept(Apple a); } class AppleHeavyApples implements AppleFormatter { @Override public String accept(Apple apple) { String characteristic = apple.getWeight() > 150 ? "heavy" : "light"; return "A " + characteristic + " " + apple.getColor() + " apple"; } } class AppleSimpleFormatter implements AppleFormatter { public String accept(Apple apple) { return "An apple of " + apple.getWeight() + "g"; } }
这个是首先定义一个接口来表示行为,不同种类的行为只需要继承这个接口,然后实现即可,可以用同一个方法来调用这个行为,而不用去写多个方法来区分不同的行为。但是此种方式有一个缺点,你需要为不同的行为分别创建不同的类。
通过匿名类进行行为参数化
public class FilteringApples { public static void main(String[] args) { List< Apple > inventory = Arrays.asList( new Apple(80,"green"), new Apple(155,"green"), new Apple(120,"red")); List< Apple > heavyApple =filterApples(inventory, new ApplePredicate() { @Override public boolean test(Apple apple) { return "red".equals(apple.getColor()); } }); System.out.println(Arrays.toString(heavyApple.toArray())); } public static List< Apple > filterApples(List< Apple > invency, ApplePredicate p){ List< Apple > result = new ArrayList< >(); for (Apple apple:invency){ if(p.test(apple)){ result.add(apple); } } return result; } } interface ApplePredicate{ public boolean test(Apple apple); }
这个使用的是匿名类来解决为不同的行为创建不同的类,但是这个会使代码不容易阅读,而且也使得代码显得冗余。