• 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();
    }
    

    小结

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

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

    参考

  • 相关阅读:
    JavaWeb项目开发案例精粹-第6章报价管理系统-04Service层
    JavaWeb项目开发案例精粹-第6章报价管理系统-03Dao层
    JavaWeb项目开发案例精粹-第6章报价管理系统-002辅助类及配置文件
    JavaWeb项目开发案例精粹-第6章报价管理系统-001需求分析及设计
    JavaWeb项目开发案例精粹-第4章博客网站系统-006View层
    JavaWeb项目开发案例精粹-第4章博客网站系统-005action层
    JavaWeb项目开发案例精粹-第4章博客网站系统-004Service层
    JavaWeb项目开发案例精粹-第4章博客网站系统-003Dao层
    JavaWeb项目开发案例精粹-第4章博客网站系统-002辅助类及配置文件
    JavaWeb项目开发案例精粹-第4章博客网站系统-001设计
  • 原文地址:https://www.cnblogs.com/woshimrf/p/java8-in-action-2.html
Copyright © 2020-2023  润新知