二、行为参数化

如何应对不断变化的需求

软件工程中,用户的需求肯定会变,如何应对不断变化的需求,理想状态下,应该把工作量降到最少,此外,类似的新功能实现还应该很简单,而且易于长期维护。
行为参数话就是可以帮助你处理频繁变更需求的一种软件开发模式:拿出一个代码块,准备好不去执行它,这个代码块以后可以被程序的其它部分调用,意味着可以推迟这块代码的执行,例如,可以将代码块作为参数传递给另一个方法,稍后再去执行它。

  • 一个例子:农场库存一堆苹果
    1.筛选绿苹果,第一个解决方案
public static List<Apple> filterGreenApples(List<Apple> appleList){
        List<Apple> result = new ArrayList<Apple>();
        for (Apple apple : appleList){
            if("green".equals(apple.getColor())){
                result.add(apple);
            }
        }
        return result;
    }

农民改主意了,还想要筛选红苹果,该怎么做?简单的解决办法就是复制这个方法,把名字改成filterRedApples,更改if匹配条件,然而,农民想要筛选多种颜色:浅绿色,暗红色,黄色等,复制的方法应付不了。

2.把颜色作为参数, 灵活适应变化

public static List<Apple> filterApplesByColor(List<Apple> appleList, String color){
        List<Apple> result = new ArrayList<>();
        for(Apple apple: appleList){
            if(apple.getColor().equals(color)){
                result.add(apple);
            }
        }
        return result;
    }

太简单了!
农民伯伯又跑来对你说,要是能区分,轻的苹果和重的苹果就太好了。

public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight){
        List<Apple> result = new ArrayList<>();
        for(Apple apple: inventory){
            if(apple.getWeight() > weight){
                result.add(apple);
            }
        }
        return result;
    }

不错的解决方案,但是,复制了大部分的代码来实现遍历库存,对每个苹果应用筛选条件,打破了DRY的软件开发原则,如果有要改变筛选的遍历方式来提升性能呢?那就得修改所有方法的实现.

3.第三次尝试:对能想到的每个属性做筛选

public static List<Apple> filterApple(List<Apple> appleList, String color, int weight, boolean flag){
        List<Apple> result = new ArrayList<Apple>();
        for(Apple apple: appleList){
            /**十分笨拙的选择颜色或重量的方法*/
            if((flag && apple.getWeight() > weight) ||
                    (!flag && apple.getColor().equals(color))){
                result.add(apple);
            }
        }
        return result;
    }
  • 首先flag含义不清晰
  • 还是不能很好的应对变化的需求:大小,形状,产地等,又怎么办。

4.行为参数化

  • 一种可能的解决方案是选择标准建模:考虑的是根据苹果的某些属性来返回一个boolean值,称只为谓词(即一个返回boolean值的函数)
    定义一个接口对选择标准建模:
interface ApplePredicate{
        public boolean test(Apple a);
    }

现在可以用ApplePredicate的多个实现代表不同的选择标准

//选择重的
static class AppleWeightPredicate implements ApplePredicate{
        public boolean test(Apple apple){
            return apple.getWeight() > 150; 
        }
    }
//选出绿的
static class AppleColorPredicate implements ApplePredicate{
        public boolean test(Apple apple){
            return "green".equals(apple.getColor());
        }
    }
选择苹果的不同策略.png
  • 刚刚做的这些和“策略设计模式”相关
    让方法接受多种行为(或战略)作为参数,并在内部使用,来完成不同的行为,这就是行为参数化。根据抽象条件筛选
public static List<Apple> filterApple(List<Apple> inventory, ApplePredicate p){
        List<Apple> result = new ArrayList<>();
        for(Apple apple : inventory){
            if(p.test(apple)){
                result.add(apple);
            }
        }
        return result;
    }
  • 灵活多了,易读易用
    filterApples方法的行为取决于通过ApplePredcate对象传递的代码,换句话说,filterApples方法的行为参数化了。
    行为参数化的好处在于,看可以把迭代要筛选的逻辑和对集合中每个元素应用的行为区分开来,这样可以重复使用一个方法,给它不同的行为来达到不同的目的。
    但是上述行为的传递是通过行为封装在对象中实现的,这样不得不声明好几个实现谓词接口的类,啰嗦且费时。

5.匿名类

List<Apple> redApples2 = filterApple(inventory, new ApplePredicate() {
            public boolean test(Apple a){
                return a.getColor().equals("red"); 
            }
        });
  • 笨重,占用代码空间
  • 经典的Java谜题

6.使用Lambda表达式

List<Apple> redApples4 = filter(inventory, 
  (Apple apple) -> "red".equals(apple.getColor()));
行为参数化.png

7.将List类型抽象化

//引入泛型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;
    }
  • 现在可以把filter 用在香蕉,桔子,Integer,Sting 列表上了

  • 行为参数化例子

//例子 排序
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
//线程执行代码块
Thread t = new Thread(() -> System.out.println("Hello"));
//GUI事件处理
button.setOnAction((ActionEvent event) ->label.setText("Sent!"));

Lambda 表达式

  • lambda
    简洁的表示一个行为或传递代码:没有名称,有参数列表,函数主体,返回类型,可能还有个可以抛出的异常列表


    Lambda表达式.png

    基本语法:
    (parameters) ->expression

    (parameters) -> {statements;}

  • 哪里及如何使用
    1.函数式接口
    只定义一个抽象方法的接口,Predicate,Comparator, Runnable等
    用函数式接口做什么呢,Lambda表达式允许直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体实现的实例),用匿名类也可以完成同样的事情,只是略笨拙。
    2.函数描述符
    函数式接口的抽象方法的签名基本上就是Lambda表达式的签名我们将这种抽象方法叫作
    函数描述符,例如:
    Runnable接口可以看作一个什么也不接受什么也不返回(void)的函数的签名,因为它只有一个叫作run的抽象方法,这个方法什么也不接受,什么也不返回(void),() -> void代表了参数列表为空,且返回void的函数

  • 付诸实践:环绕执行模式
    1.记得行为参数化
    2.使用函数式接口传递行为
    3.执行一个行为
    4.传递Lambda

  • 使用函数式接口
    java.util.function包中引入了几个新的函数式接口
    1.Predicate
    java.util.function.Predicate<T>接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。

Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(stringList, nonEmptyStringPredicate);

2.Consumer
java.util.function.Consumer<T>定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void),你如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口

forEach(Arrays.asList(1,3,4,5), (Integer i) -> System.out.println(i));

3.Function
java.util.function.Function<T, R>接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。如果你需要定义一个Lambda,将输入对象的信息映射到输出,就可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度)

List<Integer> l = map(Arrays.asList("lambda", "Eric", "stream"), (String s) -> s.length());

4.原始类型特性化
Java类型要么是引用类型(比如Byte、 Integer、 Object、 List) ,要么是原始类型(比如int、 double、 byte、 char)。但是泛型(比如Consumer<T>中的T)只能绑定到引用类型。
IntPredicate 等等等 避免使用Lambda时自动装箱,拆箱操作

  • 函数式接口 类型检查
    Lambda的类型是从使用Lambda的上下文推断出来的


    1-Lambda-类型检查.png

同一个Lambda可用于多个不同的函数式接口

  • 类型推断
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
  • 使用局部变量
    局部变量必须显式声明为final,或事实上是final。
    1.第一,实例变量和局部变量背后的实现有一个关键不同。实例变量都存储在堆中,而局部变量则保存在栈上。如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变量。因此, Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。
    2.这一限制不鼓励你使用改变外部变量的典型命令式编程模式,这种模式会阻碍很容易做到的并行处理。

  • 方法引用
    方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法,方法引用就是让你根据已有的方法实现来创建Lambda表达式


    等效方法引用.png
如何构建方法引用
1-Lambda-方法引用.png

构造函数引用
不将构造函数实例化却能够引用它,这个功能有一些有趣的应用

    static Map<String, Function<Integer, Fruit>> map = new HashMap<>();
    static{
        map.put("apple", Apple::new);
        map.put("orange", Orange::new);
    }

    public static Fruit getMeFruit(String fruit, Integer weight){
        return map.get(fruit).apply(weight);
    }
  • Lambda复合
    Java 8的好几个函数式接口都有为方便而设计的方法。具体而言,许多函数式接口,比如用于传递Lambda表达式的Comparator、Function和Predicate都提供了允许你进行复合的方法,这意味着你可以把多个简单的Lambda复合成复杂的表达式。比如,可以让两个谓词之间做一个or操作,组合成一个更大的谓词。而且,你还可以让一个函数的结果成为另一个函数的输入。
    1.比较器复合
        Comparator<Apple> c = comparing(Apple::getWeight);
        /**逆序*/
        inventory.sort(comparing(Apple::getWeight).reversed());
        /**比较器链*/
        inventory.sort(comparing(Apple::getWeight).reversed().thenComparing(Apple::getColor));

2.谓词复合

        Predicate<Apple> redApple = a -> "red".equals(a.getColor());
        Predicate<Apple> notRedApple = redApple.negate();
        Predicate<Apple> heavyApple = a -> a.getWeight() > 100;
        Predicate<Apple> redAndHeavyApple = redApple.and(heavyApple);

3.函数复合

        Function<Integer, Integer> f = x -> x + 1;
        Function<Integer, Integer> g = x -> x * 2;
        //g(f(x))
        Function<Integer, Integer> h = f.andThen(g);
        int result = h.apply(1);
        //f(g(x))
        Function<Integer, Integer> h1 = f.compose(g);
        int result2 = h1.apply(1);
  • 数学中的类似思想-积分
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容