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

  • 相关阅读:
    Manjaro mirror in china
    gnome3.X添加开机启动项
    ssh无密钥登陆的简单配置
    ESXi虚拟磁盘共享
    记录一次fat32格式U盘不识别问题
    更换内核后重编virtualbox内核模块
    宏定义字符串连接和符号粘贴
    kernel source reading notepad
    linux kernel tainted
    设计模式-观察者模式(Observer Pattern)
  • 原文地址:https://www.cnblogs.com/baidawei/p/9269972.html
Copyright © 2011-2022 走看看