应对不断变化的需求
编写能够应对变化的需求的代码并不容易。
来看一个例子,然后逐步改进这个例子,以展示一些让代码更灵活的最佳做法。
就农场库存程序而言,你必须实现一个从列表中筛选绿苹果的功能。
初试牛刀: 筛选绿苹果
第一个解决方案可能是下面这样的:
public static List<Apple> filterGreenApples (List<Apple> inventory) {List<Apple> result = new ArrayList<Apple>() ;//累积苹果的列表for (Apple apple: inventory) {if( "green" . equals (apple . getColor() )){//仅仅选出绿苹果result. add(apple) ;}}return result;}
但是现在农民改主意了,他还想要筛选红苹果。
该怎么做呢?
简单的解决办法就是复制这个方法,把名字改成filterRedApples,然后更改if条件来匹配红苹果。
然而,要是农民想要筛选多种颜色:浅绿色、暗红色、黄色等,这种方法就应付不了了。
一个良好的原则是在编写类似的代码之后,尝试将其抽象化。
再展身手:把颜色作为参数
一种做法是给方法加一个参数,把颜色变成参数,这样就能灵活地适应变化了:
public static List<App1e> filterApplesByColor (List<App1e> inventory, String color) {List <App1e> result = new ArrayList<Apple>() ;for (Apple apple: inventory) {if ( apple. getColor() . equals(color) ) {result. add(apple) ;}}return result;}
让我们把例子再弄得复杂一点。
这位农民又跑回来和你说:“要是能区分轻的苹果和重的苹果就太好了。重的苹果一般是 重量大于150克。”
作为软件工程师,你早就想到农民可能会要改变重量,于是你写了下面的方法,用另一个参数来应对不同的重量:
public static List<Apple> filterAppl esByWeight (List<App1e> inventory,int weight){List<Apple> result = new ArrayList<App1e>();for (Apple apple: inventory) {if ( apple.getweight() > weight ) {result. add (app1e) ;}}return result;}
解决方案不错,但是请注意,你复制了大部分的代码来实现遍历库存,并对每个苹果应用筛选条件。
这有点儿令人失望,因为它打破了DRY ( Don’t Repeat Yourself,不要重复自己)的软件工程原则。
如果你想要改变筛选遍历方式来提升性能呢?那就得修改所有方法的实现,而不是只改一个。
从工程工作量的角度来看,这代价太大了。
行为参数化
需要一种比添加很多参数更好的方法来应对变化的需求。
后退一步来看看更高层次的抽象。
一种可能的解决方案是对你的选择标准建模:你考虑的是苹果,需要根据Apple的某些属性(比如它是绿色的吗?重量超过150克吗? )
来返回一个boolean值。我们把它称为谓词(即一个返回boolean值的函数)。
让我们定义一个接口来对选择标准建模:
public interface ApplePredicate{boolean test (Apple app1e) ;}
现在你就可以用ApplePredicate的多个实现代表不同的选择标准了,比如
public class App1 eHeavyWeight Predicate implements ApplePredicate{ <public boolean test (Apple apple) {//仅仅选出重的苹果return apple. getWeight() > 150;}}public class App1 eGreenColorPredicate impl ements ApplePredicate{public boolean test (Apple apple) {//仅仅选出绿苹果return "green" . equals (apple. getColor() ) ;}}

多种行为,一个参数
匿名类
我们都知道,人们都不愿意用那些很麻烦的功能或概念。
目前,当要把新的行为传递给filterApples方法的时候,你不得不声明好几个实现ApplePredicate接口的类,然后实例化好几个只会提到一次的ApplePredicate对象。
这很哕嗦,也很费时间。
第四次尝试:使用匿名类
下面的代码展示了如何通过创建一个用匿名类实现ApplePredicate的对象,重写筛选的例子:
List<App1e> redApples = filterApples(inventory, new ApplePredicate() { //直接内联参数化filterapples方法的行为public boolean test (Apple apple) {return "red" . equals (apple. getColor() ) ;}}) ;
但匿名类还是不够好。第一,它往往很笨重,因为它占用了很多空间。

第六次尝试:使用Lambda表达式
上面的代码在Java 8里可以用Lambda表达式重写为下面的样子:
List<Apple> result =filterApples (inventory, (Apple app1e) -> "red" . equals (apple .getCo1or())) ;
不得不承认这代码看上去比先前干净很多。
这很好,因为它看起来更像问题陈述本身了。
我们现在已经解决了啰嗦的问题。
![7764~N%M]E]KYG%_PQ~G%Y.png
第六次尝试:将List类型抽象化
在通往抽象的路上,还可以更进一步。
目前,filterApples方 法还只适用于Apple。
你还可以将List类型抽象化,从而超越眼前要处理的问题:
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) ;}}return result;}
现在可以把filter方法用在香蕉、桔子、Integer或是string的列表上了。
现在在灵活性和简洁性之间找到了最佳平衡点,这在Java 8之前是不可能做到的。
