本文是学习Java8,参考JAVA8 IN ACTION这本书,学习整理以及自己的总结,推荐这本书;
如何对你的代码加以改进,从而更灵活地适应不断变化的需求?
复制代码
1:概念
行为参数化就是可以帮你处理频繁变更的需求的一种软件开发模式.
一言以蔽之,它意味 着拿出一个代码块,把它准备好却不去执行它。这个代码块以后可以被你程序的其他部分调用, 这意味着你可以推迟这块代码的执行。例如,你可以将代码块作为参数传递给另一个方法,稍后 再去执行它。这样,这个方法的行为就基于那块代码被参数化了
2:应对不断变化的需求
编写能够应对变化的需求的代码并不容易。让我们来看一个例子,我们会逐步改进这个例子, 以展示一些让代码更灵活的最佳做法。就农场库存程序而言,你必须实现一个从列表中筛选绿苹 果的功能。听起来很简单吧? 新建一个Apple实体类
@Getter
@Setter
@AllArgsConstructor
@ToString
public class Apple {
private int weight;
private String color="";
}
复制代码
初始化一个苹果仓库
/**
* 初始化苹果仓库
* @return
*/
public static List<Apple> initInventory() {
ArrayList<Apple> inventory = new ArrayList<>();
inventory.add(new Apple(100, "green"));
inventory.add(new Apple(200, "red"));
inventory.add(new Apple(300, "green"));
inventory.add(new Apple(400, "red"));
inventory.add(new Apple(150, "green"));
inventory.add(new Apple(250, "green"));
inventory.add(new Apple(201, "green"));
inventory.add(new Apple(350, "green"));
return inventory;
}
复制代码
第一个解决方案可能是这样的:
/**
*筛选绿苹果
* @param inventory 苹果仓库
* @return
*/
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 条件来匹配红苹果。然而,要是农民想要筛选多种颜色:浅绿色、暗红色、黄色等,这种方法 就应付不了了。一个良好的原则是在编写类似的代码之后,尝试将其抽象化。
/**
* 把颜色参数化,应对颜色变化的需求
* @param inventory
* @param color
* @return
*/
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;
}
复制代码
这时,农民不断提新需求,按重量刷选,按装量和颜色筛选,于是你不断修改代码,粘贴复制, 一种把所有属性结合起来的笨拙尝试如下所示:
/**
* 把颜色,重量等参数化,应对多种变化的需求
* @param inventory
* @param color
* @param weight
* @param flag
* @return
*/
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;
}
复制代码
这个解决方案再差不过了。首先,客户端代码看上去糟透了。 true 和 false 是什么意思?此 外,这个解决方案还是不能很好地应对变化的需求。如果这位农民要求你对苹果的不同属性做筛 选,比如大小、形状、产地等,又怎么办?而且,如果农民要求你组合属性,做更复杂的查询, 比如绿色的重苹果,又该怎么办?你会有好多个重复的 filter 方法,或一个巨大的非常复杂的 方法。
但如今这种情况下,你需要一种更好的方式,来把 苹果的选择标准告诉你的 filterApples 方法
3:行为参数化
你需要一种比添加很多参数更好的方法来应对变化的需求。让 我们后退一步来看看更高层次的抽象。
一种可能的解决方案是对你的选择标准建模:你考虑的 是苹果,需要根据 Apple 的某些属性(比如它是绿色的吗?重量超过150克吗?)来返回一个 boolean 值。我们把它称为谓词(即一个返回 boolean 值的函数)。让我们定义一个接口来对选 择标准建模:
public interface ApplePredicate {
boolean test (Apple apple);
}
复制代码
现在你就可以用 ApplePredicate 的多个实现代表不同的选择标准了
/**
* 选出重苹果的实现类
*
**/
public class AppleHeavyWeightPredicate implements ApplePredicate{
@Override
public boolean test(Apple apple) {
return apple.getWeight()>150;
}
}
复制代码
/**
* 选出绿苹果的实现类
*
**/
public class AppleGreenColorPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return "green".equals(apple.getColor());
}
}
复制代码
但是,该怎么利用 ApplePredicate 的不同实现呢?
你需要 filterApples 方法接受 ApplePredicate 对象,对 Apple 做条件测试。这就是行为参数化:让方法接受多种行为(或战 略)作为参数,并在内部使用,来完成不同的行为。
filter 方法看起来是这样的:
/**
* 根据抽象条件进行筛选
* @param inventory
* @param applePredicate
* @return
*/
public static List<Apple> filter(List<Apple> inventory, ApplePredicate applePredicate){
ArrayList<Apple> list = new ArrayList<>();
for (Apple apple:list){
if(applePredicate.test(apple)){
list.add(apple);
}
}
return list;
}
复制代码
你已经做成了一件很酷的事: filterApples 方法的行为取决于你通过 ApplePredicate 对象传递的代码。换句话说,你把 filterApples 方法的行为参数化了!
请注意,在上一个例子中,唯一重要的代码是 test 方法的实现,如图2-3所示;正是它定义 了 filterApples 方法的新行为。但令人遗憾的是,由于该 filterApples 方法只能接受对象, 所以你必须把代码包裹在 ApplePredicate 对象里。你的做法就类似于在内联“传递代码”,因 为你是通过一个实现了 test 方法的对象来传递布尔表达式的。
4:对付罗嗦
到此,我们觉得已经做的很好了,我们使用行为参数化解决了一些棘手的问题. 但是,目前来说,当要把新的行为传递给 filterApples 方法的时候,你不得不声明好几个实现 ApplePredicate 接口的类,然后实例化 好几个只会提到一次的 ApplePredicate 对象。
Java有一个机制称为匿名类,它可以让你同时 声明和实例化一个类。它可以帮助你进一步改善代码,让它变得更简洁。但这也不完全令人满意。它往往很笨重,因为它占用了很多空间。
5:使用lambda表达式
//将 List 类型抽象化
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;
}
复制代码
我们这样使用它
@Test
public void test1(){
List<Apple> result = AppleUtil.filter(initInventory(), (Apple apple) -> "green".equals(apple.getColor()));
System.out.println(result);
}
复制代码
5:demo
5.1: 用 Comparator 来排序
对集合进行排序是一个常见的编程任务。比如,你的那位农民朋友想要根据苹果的重量对库 存进行排序,或者他可能改了主意,希望你根据颜色对苹果进行排序。听起来有点儿耳熟?是的, 你需要一种方法来表示和使用不同的排序行为,来轻松地适应变化的需求。
在Java 8中, List 自带了一个 sort 方法(你也可以使用 Collections.sort )。 sort 的行为 可以用 java.util.Comparator 对象来参数化,它的接口如下:
// java.util.Comparator
public interface Comparator<T> {
public int compare(T o1, T o2);
}
复制代码
因此,你可以随时创建 Comparator 的实现,用 sort 方法表现出不同的行为。比如,你可以 使用匿名类,按照重量升序对库存排序:
/**
* 对仓库的苹果按重量升序排序
*/
@Test
public void test2(){
List<Apple> inventory = initInventory();
inventory.sort(new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
});
System.out.println(inventory);
}
复制代码
如果农民改了主意,你可以随时创建一个 Comparator 来满足他的新要求,并把它传递给 sort 方法。而如何进行排序这一内部细节都被抽象掉了。用Lambda表达式的话,看起来就是 这样:
/**
* 用Lambda表达式对仓库的苹果按重量升序排序
*/
@Test
public void test3(){
List<Apple> inventory = initInventory();
inventory.sort((Apple o1,Apple o2)->o1.getWeight().compareTo(o2.getWeight()));
System.out.println(inventory);
}
复制代码
5.2:用 Runnable 执行代码块
线程就像是轻量级的进程:它们自己执行一个代码块。但是,怎么才能告诉线程要执行哪块 代码呢?多个线程可能会运行不同的代码。我们需要一种方式来代表稍候执行的一段代码。在 Java里,你可以使用 Runnable 接口表示一个要执行的代码块。请注意,代码不会返回任何结果 (即 void )
// java.lang.Runnable
public interface Runnable{
public void run();
}
复制代码
你可以像下面这样,使用这个接口创建执行不同行为的线程:
Thread t = new Thread(new Runnable() {
public void run(){
System.out.println("Hello world");
}
});
复制代码
用Lambda表达式的话,看起来是这样:
Thread t = new Thread(() -> System.out.println("Hello world"));
复制代码
6:小结
-
1.行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不 同行为的能力。
-
2.行为参数化可让代码更好地适应不断变化的要求,减轻未来的工作量。
-
3.传递代码,就是将新行为作为参数传递给方法。但在Java 8之前这实现起来很啰嗦。为接 口声明许多只用一次的实体类而造成的啰嗦代码,在Java 8之前可以用匿名类来减少。
-
4.Java API包含很多可以用不同行为进行参数化的方法,包括排序、线程和GUI处理。