Java8 in action(1) 通过行为参数化传递代码--lambda代替策略模式

猪脚:以下内容参考《Java 8 in Action》

发布:https://ryan-miao.github.io/2017/07/15/java8-in-action-2/

源码:github

需求

果农需要筛选苹果,可能想要绿色的,也可能想要红色的,可能想要大苹果(>150g),也可能需要红的大苹果。基于此等条件,编写筛选的代码。

1. 策略模式解决方案

1.1 最直观的做法

首先,已知信息是一筐苹果(List<Apple> inventory),但筛选条件多种多样。我们可以根据不同的条件写不同的方法来达到目的。比如,找出绿色的苹果:

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;
}

同样的,可以编写filterRed, filterWeight等等。但必然出现重复代码,违反软件工程原则Don't repeast yourself。而且,筛选的类也会显得臃肿。

现在,有一种更容易维护,更容易阅读的策略模式来实现这个需求。

1.2 策略模式

由于多种筛选条件的结果都是返回一个boolean值,那么可以把这个条件抽取出来,然后在筛选的时候传入条件。这个筛选条件叫做谓词

创建谓词接口:

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;
    }
}
public class AppleRedAndHeavyPredicate implements ApplePredicate {
    @Override
    public boolean test(Apple apple) {
        return "red".equals(apple.getColor()) && apple.getWeight() >150;
    }
}

筛选的方法:

public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate predicate){
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
        if (predicate.test(apple)){
            result.add(apple);
        }
    }

    return result;
}

这样,我们就可以根据不同的条件进行筛选了。

List<Apple> inventory = new ArrayList<>();
inventory.add(new Apple("red", 100));
inventory.add(new Apple("red", 200));
inventory.add(new Apple("green", 200));
List<Apple> redHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate());
Assert.assertEquals(1, redHeavyApples.size());
Assert.assertEquals(200, redHeavyApples.get(0).getWeight());

以上的代码设计方案几乎是最好理解和扩展的了,当条件发生改变的时候只要增加一个类就可以。但java8提供了更好的选择,一种你只要声明一个接口,具体实现不用管,只有当使用的时候才去关心。

1.3 方法传递

java8提供了把方法当做参数传递的能力。这样,上面的代码就可以这样写:

List<Apple> apples = filterApples(inventory, apple -> "red".equals(apple.getColor()) && apple.getWeight() > 150);
Assert.assertEquals(1, apples.size());
Assert.assertEquals(200, apples.get(0).getWeight());

除了接口声明,不需要实现接口的类。我们只需要传入一个类似匿名内部类的东西,是的,lambda表达式和匿名内部类是可以互相转换的。

如此,我们设计接口的时候只要声明一个接口作为参数,然后再调用的时候把逻辑当做参数传进去。这个在我看来就是传递方法了。就像Javascript,可以把一个方法当做参数。

与之前的设计模式相比,lambda可以不用写那么类。

1.4 新需求

现在,果农需要包装苹果。包装的方式有多种,我将包装的结果打印出来,就是打印的样式也有多种。比如:

A light green apple

或者

An apple of 150g

上面是两种打印方式,按照之前的策略模式需要创建两个类。下面采用lambda来实现。

public interface AppleFormatter {
    String format(Apple apple);
}

public class AppleOutput{
    public static void prettyPrintApple(List<Apple> inventory, AppleFormatter formatter){
        for (Apple apple : inventory) {
            String format = formatter.format(apple);
            System.out.println(format);
        }
    }
    
    public static void main(String[] args){
        List<Apple> inventory = new ArrayList<>();
        inventory.add(new Apple("red", 100));
        inventory.add(new Apple("red", 200));
        inventory.add(new Apple("green", 200));

        prettyPrintApple(inventory, new AppleFormatter() {
            @Override
            public String format(Apple apple) {
                String characteristic = apple.getWeight()>150?"heavy":"light";
                return "A " + characteristic + " " + apple.getColor() + " apple.";
            }
        });

        prettyPrintApple(inventory, apple -> "An apple of " + apple.getWeight() + "g");

    }
}

控制台打印:

A light red apple.
A heavy red apple.
A heavy green apple.
An apple of 100g
An apple of 200g
An apple of 200g

如果使用IntelIJ IDEA作为编辑器,那么肯定会忍受不了匿名内部类,因为IDEA会不停的提示你:匿名内部类可以转变为方法参数。

1.5 更普遍的用法

上面的筛选只是针对Apple的,那么是否可以推广开来呢?下面针对List类型抽象化来构造筛选条件。

创建一个条件接口:

public interface Predicate<T> {
    boolean test(T t);
}

更新一个更普遍的filter:

public static <T> List<T> filter(List<T> list, Predicate<T> p){
    List<T> result = new ArrayList<T>();
    for (T e : list) {
        if (p.test(e)){
            result.add(e);
        }
    }

    return result;
}

那么,可能这样用:

public static void main(String[] args) {
    List<Apple> appleList = new ArrayList<>();
    appleList.add(new Apple("red", 100));
    appleList.add(new Apple("red", 160));
    appleList.add(new Apple("green", 60));

    List<Apple> redApples = filter(appleList, (Apple apple) -> "red".equals(apple.getColor()));
    Assert.assertEquals(2, redApples.size());

    List<Integer> numberList = Arrays.asList(1,2,3,4,5,6,7,8,9);
    List<Integer> lessThan4Numbers = filter(numberList, (Integer num) -> num < 4);
    Assert.assertEquals(3, lessThan4Numbers.size());

}

1.6 排序

行为参数化的过程掌握后,很多东西就会自然而然的使用了。比如排序。果农需要将苹果按照大小排序呢?

java8中List是有默认方法的:

default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

其实就是将以前手动排序封装了。那么,苹果的排序就可以传入一个比较器实现:

@Test
public void sort(){
    List<Apple> appleList = new ArrayList<>();
    appleList.add(new Apple("red", 100));
    appleList.add(new Apple("red", 160));
    appleList.add(new Apple("green", 60));
    
    appleList.sort((o1, o2) -> o1.getWeight()-o2.getWeight());
}

根据IDEA的提示,进一步:

appleList.sort(Comparator.comparingInt(Apple::getWeight));

这里就涉及了多次行为传参了。后面再说。

1.7 Runnable

多线程Runnable的时候经常会采用匿名内部类的做法:

@Test
public void testRunnable(){
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("running");
        }
    };

    new Thread(runnable).start();
}

采用lambda行为传参就变为:

@Test
public void testRunnable(){
    Runnable runnable = () -> System.out.println("running");

    new Thread(runnable).start();
}

小结

本次测试主要理解如下内容:

  • 行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力。
  • 传递代码,就是将行为作为参数传递给方法。

参考

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏糊一笑

移动端效果之IndexList

写在前面 接着前面的移动端效果讲,这次讲解的的是IndexList的实现原理。效果如下: ? 代码请看这里:github 移动端效果之swiper 移动端效果之...

1974
来自专栏冰霜之地

高效的序列化/反序列化数据方式 Protobuf

上篇文章中其实已经讲过了 encode 的过程,这篇文章以 golang 为例,从代码实现的层面讲讲序列化和反序列化的过程。

965
来自专栏数据结构与算法

计算(calc.cpp)

计算(calc.cpp) 【问题描述】 小明在你的帮助下,破密了Ferrari设的密码门,正要往前走,突然又出现了一个密码门,门上有一个算式,其中只有“(”,“...

34210
来自专栏HTML5学堂

详析获取标签

在前几期的文章当中,我们学习了JS的基础语法,它是我们学习其它知识点的重要前提。小编也与大家分享了JS逻辑与DOM的相结合,并带着大家去实现了一些简单的页面交互...

3009
来自专栏函数式编程语言及工具

泛函编程(1)-泛函编程是如何实现的

  泛函编程就是把函数组合起来形成一个完整的程序。可想而知,函数组合的过程可以是曲折的,形成的程序可以是复杂的。那么泛函编程又是如何保证一个复杂的函数组合程序是...

1867
来自专栏丑胖侠

《Drools7.0.0.Final规则引擎教程》第4章 4.5RHS语法

RHS语法 使用说明 RHS是满足LHS条件之后进行后续处理部分的统称,该部分包含要执行的操作的列表信息。RHS主要用于处理结果,因此不建议在此部分再进行业务判...

2108
来自专栏desperate633

Java Generic 自定义泛型如何自定义泛型自定义泛型的边界共变性,逆变性泛型对象的比较

考虑我们要实现了一个节点对象,这个对象可以自定义类型,我们可以用泛型语法进行如下的定义:

441
来自专栏数据小魔方

左手用R右手Python系列之——字符串格式化进阶

关于R语言字符串格式化之前无论是专题还是案例教程中均有所涉及,今日这一篇之所以重提是因为又找到了一个很好用的字符串格式化包。 这个包的语法源于Python风格,...

27912
来自专栏blackheart的专栏

实用代码-JavaScript实用小函数一枚(深入对象取值)

适用场合描述 做web开发时使用JSON作为数据传输格式时,如果服务器端传回的JSON数据对象层次嵌套过深。比如下面这段: 1 var data = { 2 ...

1927
来自专栏代码散人

ios开发 Runtime 详解part2(动态方法解析)

在 ios开发 Runtime 详解part1中我已经解释了Introspection,接下来介绍Runtime的其它特性。

571

扫描关注云+社区