zoukankan      html  css  js  c++  java
  • Java 8 (1) 行为参数化

      行为参数化就是可以帮助你处理频繁变更需求的一种软件开发模式。它意味着拿出一个代码块,把它准备好却不去执行它。这个代码块以后可以被你程序的其他部分调用,这意味着你可以推迟这块代码的执行。例如:你可以将代码块作为参数传递给另一个方法,稍后再去执行它。

    应对不断变化的需求

    1.第一次尝试:实现一个功能,从一个列表中筛选出绿色苹果的功能。

    首先准备Apple实体类

    public class Apple {
        private Integer Id;
        private String Color;
        private Double Weight;
    
        //getter..setter..toString..
    }

    编写过滤出绿色苹果的功能

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

    输出结果:

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

    现在只需要这样调用,产品就会满意了。

    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 包含很多可以用不同行为进行参数化的方法、包括排序、线程等。

  • 相关阅读:
    docker 安装mysql
    Java web项目搭建系列之二 Jetty下运行项目
    Java web项目搭建系列之一 Eclipse中新建Maven项目
    Maven 添加其他Maven组件配置问题
    C# 中定义扩展方法
    Oracle 函数
    【Webservice】2 counts of IllegalAnnotationExceptions Two classes have the same XML type name
    Linux精简版系统安装网络配置问题解决
    Rsync 故障排查整理
    Failed to set session cookie. Maybe you are using HTTP instead of HTTPS to access phpMyAdmin.
  • 原文地址:https://www.cnblogs.com/baidawei/p/9269972.html
Copyright © 2011-2022 走看看