行为参数化就是能够帮助你处理频繁变动需求的一种软件开发模式。它意味着拿出一个代码块,把它准备好却不去执行它。这个代码块之后能够被你程序的其余部分调用,这意味着你能够推迟这块代码的执行。例如:你能够将代码块做为参数传递给另外一个方法,稍后再去执行它。java
应对不断变化的需求算法
1.第一次尝试:实现一个功能,从一个列表中筛选出绿色苹果的功能。设计模式
首先准备Apple实体类app
public class Apple { private Integer Id; private String Color; private Double Weight; //getter..setter..toString.. }
编写过滤出绿色苹果的功能ide
public static List<Apple> filter(List<Apple> apples){ List<Apple> result = new ArrayList<>(); for(Apple apple : apples){ //筛选出绿色的苹果 if("green".equals(apple.getColor())){ result.add(apple); } } return result; }
测试数据测试
public static void main(String[] args) { List<Apple> apples = Arrays.asList( new Apple(1,"green",18.0), new Apple(2,"yellow",36d), new Apple(3,"red",42d), new Apple(4,"green",15d), new Apple(5,"red",16d) ); List<Apple> greenApple = filter(apples); System.out.println(greenApple); }
输出结果:spa
[Apple{Id=1, Color='green', Weight='18.0'}, Apple{Id=4, Color='green', Weight='15.0'}]
实现功能了,如今产品说我又想要筛选红色的苹果了,最简单的办法就是复制这个方法,把名字改为filterRedApples,而后更改if条件来匹配红苹果,然而产品想要筛选更多颜色的苹果,黄色、橘黄色、大酱色等,这种方法就不行了,将颜色做为参数线程
2.第二次尝试:把颜色做为参数设计
//把颜色做为参数 public static List<Apple> filterApplesByColor(List<Apple> apples,String color){ List<Apple> result = new ArrayList<>(); for(Apple apple : apples){ if(color.equals(apple.getColor())){ result.add(apple); } } return result; }
如今只须要这样调用,产品就会满意了。code
List<Apple> yellowApple = filterApplesByColor(apples,"yellow");
List<Apple> redApple = filterApplesByColor(apples,"red");
而后产品又跑过来讲,要是能区分苹果大小就太好了,把大于32的分为大苹果,小于32的分为小苹果。
因而你下了下面的方法,又增长了重量的参数
//根据重量来筛选苹果 public static List<Apple> filterApplesByWeight(List<Apple> apples,Double weight){ List<Apple> result = new ArrayList<>(); for(Apple apple : apples){ if(apple.getWeight() > weight){ result.add(apple); } } return result; }
你终于实现了产品的需求,可是请注意,你复制了大部分的代码来实现遍历苹果列表,并对每一个苹果筛选条件。这有点使人失望,由于它打破了Don't Repeat Yourself(不要重复你本身)的软件工程原则。
3.第三次尝试:把你能想到的每一个属性作筛选
你能够将颜色和重量结合为一个方法,称为filterColorOrWeight,而后增长一个参数来区分须要筛选哪一个属性。
//按颜色筛选或按重量筛选 public static List<Apple> filterColorOrWeight(List<Apple> apples, String color, Double weight, boolean flag) { List<Apple> result = new ArrayList<>(); for (Apple apple : apples) { if ((flag && color.equals(apple.getColor())) || (!flag && apple.getWeight() > weight)) { result.add(apple); } } return result; }
而后你能够这样使用:
List<Apple> yellowApples = filterColorOrWeight(apples, "yellow",0d,true); List<Apple> bigApples = filterColorOrWeight(apples, "",32d,false);
这个解决方案再差不过了,首先客户端代码看上去烂透了,true和false是什么意思?此外,这个解决方案仍是不能很好的区应对变化的需求,若是产品又让你对苹果的其余不一样的属性作筛选,好比大小、形状、产地等,又该怎么办?或者要求你组合属性,做更复杂的查询,好比绿色的大苹果,又该怎么办?
行为参数化
你在上一节中看到了,你须要一种比添加不少参数更好的方法来应对变化的需求。 一种解决方案是对你选择的标准建模:好比根据Apple的某些属性(是不是绿色,是否大于32)来返回一个boolean值,咱们把它称之为谓词。首先咱们来定义一个借口来对选择标准建模。
public interface ApplePredicate { boolean test(Apple apple); }
如今就可使用ApplePredicate的多个实现来表明不一样的选择标准了,好比:
public class AppleGreenColorPredicate implements ApplePredicate { //绿苹果谓词 public boolean test(Apple apple) { return "green".equals(apple.getColor()); } }
public class AppleHeavyWeightPredicate implements ApplePredicate { //大苹果谓词 public boolean test(Apple apple) { return apple.getWeight() > 32; } }
如今,你能够把这些标准看作filter方法的不一样行为。你刚作的这些和“策略设计模式”相关,它让你定义一组算法,把他们封装起来(成为“策略”),而后在运行时选择一个算法。在这里,ApplePredicate就是算法组,不一样的策略就是AppleHeavyWeightPredicate和AppleGreenColorPredicate。
你须要filterApples方法接受ApplePredicate对象,对Apple作条件测试。这就是行为参数化:让方法接受多种行为(或战略)做为参数,并在内部使用,来完成不一样的行为。
4.第四次尝试:根据抽象条件筛选
//根据抽象条件筛选 public static List<Apple> filterApples(List<Apple> apples,ApplePredicate p){ List<Apple> result = new ArrayList<>(); for(Apple apple : apples){ if(p.test(apple)){ result.add(apple); } } return result; }
使用的时候这样:
List<Apple> greenApple = filterApples(apples,new AppleGreenColorPredicate()); List<Apple> bigApple = filterApples(apples,new AppleHeavyWeightPredicate());
1.传递代码/行为
到这里能够小小的庆祝一下了,这段代码比咱们第一次尝试的时候灵活多了,读起来、用起来也更容易!如今你能够建立不一样的ApplePredicate对象,并将他们传递给filterApples方法。好比如今产品让你找出全部重量超过80克的红苹果,你只需建立一个类来实现ApplePredicate就好了,你的代码如今足够灵活,能够应对任何设计苹果属性的需求变动了:
public class AppleRedAndBigPredicate implements ApplePredicate { @Override public boolean test(Apple apple) { return "red".equals(apple.getColor()) && apple.getWeight() > 80; } }
传递给filterApples方法,无需修改filterApples方法的内部实现:
List<Apple> redBigApple = filterApples(apples,new AppleRedAndBigPredicate());
如今你作了一件很酷的事:filterApples方法的行为取决于你经过ApplePredicate对象传递的代码。换句话说,你把filterApples方法行为参数化了!
在上一个例子中,惟一重要的代码是test方法的实现,正式它定义了filterApples方法的新行为。因为filterApples方法只能接受对象,因此你必须把代码包裹在ApplePredicate对象里。这种作法相似于在内联“传递代码”,由于你是经过一个实现了test方法的对象来传递布尔表达式的。
2.多种行为,一个参数
行为参数化的好处在于你能够把遍历集合的逻辑和 对集合中每一个元素的判断逻辑 区分开来。这样就能够重复使用同一个方法,给它不一样的行为来达到不一样的目的。
行为参数化练习
编写printApple方法,实现一个功能,以多种方式根据苹果生成一个String输出(例如,可让printApple方法只打印每一个苹果的颜色,或者让它打印什么颜色的大(小)苹果)
建立AppleFormater接口
public interface AppleFormater { String accept(Apple a); }
建立AppleWeightFormater、AppleColorFormater 来实现接口
public class AppleWeightFormater implements AppleFormater { @Override public String accept(Apple a) { return "这是一个" + a.getColor() + "的" + (a.getWeight() > 32 ? "大" : "小") + "苹果"; } }
public class AppleColorFormater implements AppleFormater { @Override public String accept(Apple a) { return "一个" + a.getColor() + "的苹果"; } }
而后建立printApple方法,给它传递不一样的formater对象
public static void printApple(List<Apple> apples,AppleFormater af) { for (Apple apple : apples) { String output = af.accept(apple); System.out.println(output); } }
测试:
printApple(apples, new AppleWeightFormater()); printApple(apples, new AppleColorFormater());
如今,你能够把行为抽象出来了,让你的代码更加适应需求的变化,可是这个过程很啰嗦,由于你须要声明不少只要实例化一次的类。
匿名类
使用匿名类来对付这些只须要使用一次的类。
好比说使用匿名类来新增长一个打印样式为:哇塞,这是一个大苹果啊?
printApple(apples, new AppleFormater() { public String accept(Apple a) { return "哇塞,这是一个" + (a.getWeight() > 32 ? "大" : "小") + "苹果啊?"; } });
使用匿名类虽然解决了为一个接口声明好几个实体类的啰嗦问题,但它仍然不能使人满意。
使用Lambda表达式
上面的代码能够用Lambda表达式重写为下面的样子:
printApple(apples, (Apple a) -> "哇塞,这是一个" + (a.getWeight() > 32 ? "大" : "小") + "苹果啊?");
这样看起来是否是更清爽多了?更像是在描述问题自己!关于lambda将会在下节介绍。
将List类型抽象化
目前filterApples方法还只适用于Apple,能够将List类型抽象化,让它支持香蕉、橘子、Integer或是String的列表上!
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; }
测试:
List<Apple> greenApple = filter(apples,(Apple apple) -> "green".equals(apple.getColor())); System.out.println(greenApple); List<Integer> evenNumbers = filter(Arrays.asList(1,2,3,4,5,6,10),(Integer i) -> i%2 ==0); System.out.println(evenNumbers);
帅不帅?你如今在灵活性和间接性之间找到了最佳的平衡点,这在java 8以前是不可能作到的!
真实的例子
你如今已经看到,行为参数化是一个颇有用的模式,它可以轻松的适应不断变化的需求。这种模式能够把一个行为(一段代码)封装起来,并经过传递和使用穿件的行为(例如对Apple的不一样谓词)将方法的行为参数化。
1.使用Comparator来排序
对集合排序是一个常见的任务,好比,产品过来讲想按照苹果的重量进行排序。在java 8中,List自带了一个sort方法(也可使用Collections.sort)。sort的行为能够用java.util.Comparator对象来参数化,它的接口以下:
package java.util; @FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); }
所以,你能够随时建立Comparator的实现,用sort方法来排序,使用匿名类按照重量升序排序:
apples.sort(new Comparator<Apple>() { @Override public int compare(Apple o1, Apple o2) { return o1.getWeight().compareTo(o2.getWeight()); } });
使用lambda以下:
apples.sort((Apple a1,Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
2.用Runnable执行代码块
线程就像是轻量级的进程:它们本身执行一个代码块。在Java离可使用Runnable接口表示一个要执行的代码块。
package java.lang; @FunctionalInterface public interface Runnable { public abstract void run(); }
使用匿名类建立执行不一样行为的线程:
Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println("t111"); } });
使用Lambda:
Thread t2 = new Thread(() -> System.out.println("t2222"));
小结:
1.行为参数化,就是一个方法接受多个不一样的行为做为参数,并在内部使用它们,完成不一样行为的能力。
2.行为参数化可以让代码更好地适应不断变化的需求,减轻将来的工做量。
3.传递代码,就是将新行为做为参数传递给方法,在java 8 以前这实现起来很啰嗦。为接口声明许多只用一次的实体类而形成的啰嗦代码,在java 8 以前可使用匿名类来减小。
4.java API 包含不少能够用不一样行为进行参数化的方法、包括排序、线程等。