zoukankan      html  css  js  c++  java
  • 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();
    }
    

    小结

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

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

    参考

  • 相关阅读:
    Druid 使用 Kafka 将数据载入到 Kafka
    Druid 使用 Kafka 数据加载教程——下载和启动 Kafka
    Druid 集群方式部署 —— 启动服务
    Druid 集群方式部署 —— 端口调整
    Druid 集群方式部署 —— 配置调整
    Druid 集群方式部署 —— 配置 Zookeeper 连接
    Druid 集群方式部署 —— 元数据和深度存储
    Druid 集群方式部署 —— 从独立服务器部署上合并到集群的硬件配置
    Druid 集群方式部署 —— 选择硬件
    Druid 独立服务器方式部署文档
  • 原文地址:https://www.cnblogs.com/woshimrf/p/java8-in-action-2.html
Copyright © 2011-2022 走看看