Java 8 (1) 行为参数化

  行为参数化就是能够帮助你处理频繁变动需求的一种软件开发模式。它意味着拿出一个代码块,把它准备好却不去执行它。这个代码块之后能够被你程序的其余部分调用,这意味着你能够推迟这块代码的执行。例如:你能够将代码块做为参数传递给另外一个方法,稍后再去执行它。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 包含不少能够用不一样行为进行参数化的方法、包括排序、线程等。

相关文章
相关标签/搜索