原文链接:Java8 – 01 – 行为参数化
相关文章:
Java8中出现了许多新特性,行为参数化就是其中之一,所谓行为参数化,就是可以帮助你处理频繁变更的需求的一种软件开发模式;简单来说,就是拿出一个代码块,把它准备好却不执行它,这个代码块以后可以被程序的其他部分调用,这就意味着我们可以推迟这个代码块的执行
在软件开发中,一个众所周知的问题就是,不管你做什么,需求总是会在变。比如说,有个程序帮助农民了解自己的库存,第一天农民想知道库存中绿色的苹果有多少个,你写好了代码,但第二天,农民想知道自己的库存中有多少重量大于150克的,这次你又修改好了代码,之后农民又想知道绿色的和重量大于150克的苹果有多少,这就需要我们修改多次程序。理想状态下,我们应该把工作量降到最少,此外,类似的新功能实现起来还应该简单,而且易于长期维护
以下是一个经典的例子:筛选苹果,通过这个例子,我们可以很清晰地看到行为参数化带来的简便之处
-
Apple.java
public class Apple { private String color; private Integer weight; public Apple(String color, Integer weight) { this.color = color; this.weight = weight; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public Integer getWeight() { return weight; } public void setWeight(Integer weight) { this.weight = weight; } @Override public String toString() { return "Apple{" + "color='" + color + '\'' + ", weight=" + weight + '}'; } }
例子一
-
假设我们有一个 Apple 类,它有 color 和 weight 两个属性,此时我们还有一个变量 inventory 保存着一个 Apple 的列表,此时我们需要帮助农民筛选出所有的绿苹果,这很简单我们可以这么做
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; }
例子二
-
此时农民又改变主意了,他想要筛选出所有的红苹果,这个也简单,只需要将上面的代码中 green 改成 red 就可以了,可万一农民又想要其他颜色苹果,那样的话又要改动代码,因此我们可以将颜色给抽象化
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; }
例子三
-
这时候农民又来了,说是要帮他筛选出重量大于 150 克的苹果,好吧,之前写的方法都用不到了,重新写一个吧,同时我们将重量给抽象化
public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight) { List<Apple> result = new ArrayList<>(); for (Apple apple : inventory) { if (apple.getWeight() > weight) { result.add(apple); } } return result; }
例子四
-
过了一天,农民又说,再帮我筛选下重量大于 150 克的红苹果,我的天呐又改需求,有完没完啊,也就在内心吐槽一下,转头又去撸代码了
public static List<Apple> filterApples(List<Apple> inventory, String color, int weight) { List<Apple> result = new ArrayList<>(); for (Apple apple : inventory) { if (apple.getColor().equals(color) && apple.getWeight() > weight) { result.add(apple); } } return result; }
例子五
-
这几天,农民都没有来,过了几天潇洒的生活,可转眼一想不对啊,万一哪天农民又来了,那我们不是还是要重构代码,有没有什么其他好的办法呢?
-
这个时候,行为参数化就出场了,我们考虑的是苹果,需要根据苹果的某些属性来返回一个 boolean 值,我们称之为谓词 (即一个返回 boolean 值的函数),因此我们先定义一个接口来对选择标准建模
public interface ApplePredicate { boolean test(Apple apple); } ``
-
此时,我们就可以通过使用 ApplePredicate 的多个实现来表示不同的选择标准了,我们可以将这些标准看作是 filter 方法的不同行为,这与策略模式 (定义一族算法,把它们封装起来,可以称之为策略,然后在运行时选择一个算法) 十分相近,在这里,算法族就是 ApplePredicate,不同的策略就是不同的具体实现
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate predicate) { List<Apple> result = new ArrayList<>(); inventory.forEach(apple -> { if (predicate.test(apple)) { result.add(apple); } }); return result; }
public class AppleGreenColorPredicate implements ApplePredicate { @Override public boolean test(Apple apple) { return "green".equals(apple.getColor()); } }
-
如上所示,这段代码已经比以前灵活多了,我们可以创建不同的 ApplePredicate 对象,并将它们传递给 filterApples 方法,filterApples 方法的行为取决于我们通过 ApplePredicate 对象传递的代码,换句话说,我们已经把 filterApples 方法的行为参数化了
例子六
-
但此时我们又会发现,如果使用例子五中的代码,会声明许多只要实例化一次的类,这样会导致十分繁琐,因此我们可以尝试使用匿名内部类来进行改进
List<Apple> result = filterApples(inventory, new ApplePredicate() { @Override public boolean test(Apple apple) { return "green".equals(apple.getColor()); } });
例子七
-
但使用匿名内部类还是不够友好,第一、它往往很笨重,因为它占用了很多空间;第二、会让人看起来很费解,因此我们需要进一步的改进,使用 Lambda 表达式
List<Apple> result = filterApples(inventory, (Apple apple) -> "green".equals(apple.getColor()));
-
还可以将类型去掉,进一步简便
List<Apple> result = filterApples(inventory, apple -> "green".equals(apple.getColor()));
-
到了这一步,我们会发现只需要一行代码就可以用来代替之前的多行代码,是不是干净很多了呢!
例子八
-
当前 filterApples 方法还只适用于 Apple,可万一哪一天,农民朋友说再帮我筛选下香蕉、西瓜等等其他水果,那又该怎么办呢?所以我们可以更进一步,将 List 类型抽象化,从而超越眼前要处理的问题
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) { List<T> result = new ArrayList<>(); list.forEach(t -> { if (predicate.test(t)) { result.add(t); } }); return result; }
-
现在可以说是大功告成了,filter 方法已经基本满足了农民朋友的所有需求了,是不是很开心~