java学习笔记(三)
简单的讲讲行为参数化传递代码,这也是Java8实战的第二章
应对不断变化的需求
在软件工程中,一个从所周知的问题就是,不管你做什么,用户的需求总是会变的(PM的需求总是会变的)。编写能够应对变化需求的代码并不容易。行为参数化就是要帮助你处理频繁更变的需求的一种软件开发模式。一言以蔽之,它意味着拿出一个代码块,把它准备好却不去执行它。这个代码块以后可以被你程序的其他部分调用,这意味着你可以推迟这块代码的执行。例如,你可以将代码块作为参数传递给另外一个方法,稍后再去执行它。这样,这个方法的行为就基于那块代码被参数化了。
我们现在来看一个例子,查找库存中所有绿色苹果。
初试牛刀:筛选绿苹果
可能你选择最初的解决方案就是这样:
private static List<Apple> filterGreenApples(List<Apple> apples) {
List<Apple> result = new ArrayList<Apple>();
for (Apple apple : apples) {
if ("green".equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
一个 if 语句就能达到我们想要的功能,如果现在还需要筛选红色的苹果呢?只需要简单的复制这个方法,改变函数名和 if 条件也能达到目的。但是如果要筛选各种颜色的苹果呢?你就需要考虑将其抽象化,以应变不断变化的需求。
再展身手:把颜色作为参数
给方法加一个颜色参数,这样就能适应变化了:
private static List<Apple> filterApplesByColor(List<Apple> apples, String color) {
List<Apple> result = new ArrayList<Apple>();
for (Apple apple : apples) {
if (color.equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
是不是比前面的简单了?但是现在有需要筛选重量超过150克的苹果,你会不假思索的写下:
private static List<Apple> filterApplesByWeight(List<Apple> apples, int weight) {
List<Apple> result = new ArrayList<Apple>();
for (Apple apple : apples) {
if (apple.getWeight() > weight) {
result.add(apple);
}
}
return results;
}
解决方法很简单,但是你复制了大部分的代码来实现遍历库存,并对每个苹果应用筛选条件。这样破坏了DRY(Don’t Repeat Yourself 不要重复自己)的软件工程原则。你需要考虑如何将颜色和重量结合成一个方法。
第三次尝试:对你能想到的每个属性做筛选
一个笨拙的尝试:
private static List<Apple> filterApples(List<Apple> apples, String color, int weight, boolean flag) {
List<Apple> result = new ArrayList<Apple>();
for (Apple apple : apples) {
if ((flag && apple.getWeight() > weight) || (!flag && color.equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
这是个非常糟糕的解决方案,不明语义的 flag ,如果再增加属性,那又该怎么解决呢?需要组合筛选又怎么办呢?为了实现灵活的筛选,可以考虑如何将行为参数化。
行为参数化
现在需要一种比添加很多参数更好的方法来应对变化的需求。让我们退一步来看看更高层次的抽象。一种可能解决方案是对你的悬着标准建模:你考虑的是苹果,需要根据Apple的某些属性(比如它是绿色的吗?重量超过150克吗?)来返回一个布尔值。我们把它称为谓词。让我们定义一个接口来对选择标准建模:
public interface ApplePredicate {
boolean test(Apple apple);
}
这样就可以实现不同的选择标准了
选绿色苹果:
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;
}
}
你可以把这些标准看作 filter 方法的不同行为。这些和“策略设计模式”相关,它让你定义一族算法,把他们封装起来(称为“策略”),然后运行时选择一个算法。下面就是将filterApples方法让它接受一个ApplePredicate对象就可以了。
第四次尝试: 根据抽象条件筛选
新的filterApples方法是这样的:
private static List<Apple> filterApples(List<Apple> apples, ApplePredicate<Apple> applePredicate) {
List<Apple> result = new ArrayList<>();
for (Apple apple : apples) {
if (applePredicate.test(apple)) {
result.add(apple);
}
}
return appleList;
}
现在代码已经灵活多了,读起来也很简单明了。现在你可以创建不同的ApplePredicate对象,并将它们传递给filterApples方法。这样就可以根据不同的条件来创建一个类并且实现ApplePredicate就可以了。
选择红苹果:
public class AppleRedAndHeavyPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return "red".equals(apple.getColor()) && apple.getWeight() > 150;
}
}
List<Apple> filterApples = filterApples(inventory, new AppleRedAndHeavyPredicate());
现在filterApples方法的行为已经取决于通过ApplePredicate对象来实现了。这就是行为参数化了!行为参数化的好处在与你可以把迭代要筛选的集合的逻辑与对集合中每个元素应用的行为区分开来。这样你就可以重复使用同一个方法,给它不同的行为来达到不同的目的。
但是很令人遗憾的是,filterApples方法只接受对象,你的代码必须包裹在对象里以内联的方式”传递代码“。你已经看见了,可以把行为抽象出来让你的代码适应需求变化,但这个过程很啰嗦。
对付啰嗦
现在当要把新行为传递给 filterApples 方法的时候,你不得不声明几个ApplePredicate接口的实现,然后实例化。这很浪费时间。有没有更简单的方式呢?Java 有一种匿名类的机制,你可以同时声明并实例化一个类。
第五次尝试:使用匿名类
下面是使用匿名类重筛选红苹果的例子:
List<Apple> filterApples = filterApples(inventory, new ApplePredicate(){
@Override
public boolean test(Apple apple) {
return "red".equals(apple.getColor());
}
});
又进一步简化了,但是匿名类还是不够好。匿名类有些笨重,有着过多的模板代码。有时候会令人费解,这里就不举例子了。
这里你是不是想到什么了,既然匿名类还不够简洁,那使用匿名表达式(lambda)呢?
第六次尝试:使用Lambda表达式
使用lambda来简化代码,筛选红苹果:
List<Apple> filterApples = filterApples(inventory, apple -> "red".equals(apple.getColor());
通过Lambda的方式来筛选红苹果并且是重苹果:
List<Apple> filterApples = filterApples(inventory, apple -> "red".equals(apple.getColor()) && apple.getWeight() > 150);
不得不承认这代码已经很干净了,就只是简单的描述问题本身。现在我们可以在通往抽象的路上更进一步,就是把filterApples方法和ApplePredicate接口泛化,是他们不仅仅适用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 方法用在香蕉,葡萄等水果和其他列表上了。这就是Java8 给我们带来的,灵活性和简洁性之间微妙的平衡点。
总结
- 行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力。
- 行为参数化可以让代码更好的适应不断变化的要求,减轻工作量。
- 传递代码,就是将新行为作为参数传递给方法。但在Java8之前这实现起来很啰嗦。为接口生命许多只是用一次的实体类而造成的啰嗦代码,在Java8之前采用匿名类来减少。
- JavaAPI包含了很多可以用不同行为进行参数化的方法,包括排序、线程等。
网友评论