《Java8實戰》-讀書筆記第二章

經過行爲參數化傳遞代碼

行爲參數化

在《Java8實戰》第二章主要介紹的是經過行爲參數化傳遞代碼,那麼就來了解一下什麼是行爲參數化吧。git

在軟件工程中,一個從所周知的問題就是,無論你作什麼,用戶的需求老是會變的(PM的需求老是會變的)。比方說,有個應用程序是幫助農民瞭解本身的庫存。這位農民可能想有一個查找庫存中全部綠色蘋果的功能。可是到了次日,他忽然告訴你:「其實我還想找出全部重量超過150克的蘋果。」,你一想簡單嘛不就是改一下條件而已。因而過了兩天,他又說:「要是我能夠篩選即便綠色的蘋果,重量也超過150克的蘋果。」,這樣頻繁的改需求也不太好,面對這樣的狀況理想狀態下應該把工做量降到最低。此外,相似的功能實現起來應該仍是很簡單,並且利於長期維護。github

行爲參數化就是要幫助你處理頻繁更變的需求的一種軟件開發模式。一言以蔽之,它意味着拿出一個代碼塊,把它準備好卻不去執行它。這個代碼塊之後能夠被你程序的其餘部分調用,這意味着你能夠推遲這塊代碼的執行。例如,你能夠將代碼塊做爲參數傳遞給另一個方法,稍後再去執行它。這樣,這個方法的行爲就基於那塊代碼被參數化了。設計模式

應對不斷變化的需求

想要編寫能應對變化的需求並不容易。讓咱們來看一個例子,咱們將會逐漸的改進這個例子,以展現一些讓代碼更靈活的作法。就像農場庫存程序而言,你須要實現一個從列表中篩選綠蘋果的功能。bash

篩選蘋果

  1. 篩選綠蘋果,可能你選擇最初的解決方案就是這樣:
private static List<Apple> filterGreenApples(List<Apple> apples) {
    List<Apple> appleList = new ArrayList<Apple>();
    for (Apple apple : apples) {
        if ("green".equals(apple.getColor())) {
            appleList.add(apple);
        }
    }
    return appleList;
}
複製代碼

如今代碼中就是篩選綠蘋果。但如今農民改主意了,他還想要篩選紅蘋果。按照最簡單的方法就是,把方法複製一下而且改一下條件爲篩選紅蘋果的條件。是的,這樣作起來很簡單,要是農民想要篩選多種顏色:青色、深紅、淡紅...這種方法就不太適合了。app

  1. 優化代碼,經過顏色做爲參數篩選蘋果:
private static List<Apple> filterApplesByColor(List<Apple> apples, String color) {
    List<Apple> appleList = new ArrayList<Apple>();
    for (Apple apple : apples) {
        if (color.equals(apple.getColor())) {
            appleList.add(apple);
        }
    }
    return appleList;
}
複製代碼

很簡單對吧。如今,農民又有想法:「能篩選出輕蘋果和重蘋果就好啦!通常重蘋果的重量是150克。」你可能早就想到了須要經過重量來篩選蘋果,因而你又把參數穿進來做爲條件進行篩選。ide

  1. 將重量做爲參數,進行重蘋果篩選:
private static List<Apple> filterApplesByWeight(List<Apple> apples, int weight) {
    List<Apple> appleList = new ArrayList<Apple>();
    for (Apple apple : apples) {
        if (apple.getWeight() > weight) {
            appleList.add(apple);
        }
    }
    return appleList;
}
複製代碼

是的,解決方法很簡單,可是你複製了大部分的代碼來實現遍歷庫存,並對每一個蘋果應用篩選條件。這樣破壞了DRY(Don't Repeat Yourself 不要重複本身)的軟件工程原則。或許,你一下就想到了這辦法,將全部的參數都放在一個方法中,這樣就能夠簡化不少代碼了。測試

  1. 第三次嘗試,對你能想到的每一個屬性作篩選:
private static List<Apple> filterApples(List<Apple> apples, String color, int weight, boolean flag) {
    List<Apple> appleList = new ArrayList<Apple>();
    for (Apple apple : apples) {
        boolean result = (flag && apple.getWeight() > weight) || (!flag && color.equals(apple.getColor()));
        if (result) {
            appleList.add(apple);
        }
    }
    return appleList;
}
複製代碼

代碼看起來很簡單,可是感受倒是不太好。若是不把註釋寫清楚,別人閱讀你代碼時根本就不知道flag是幹嗎用的。要是,農民忽然又有個想法,需用經過大小、形狀、產地等條件來進行篩選怎麼辦?因此,咱們須要利用行爲參數化來解決這個問題,提升代碼的靈活性。優化

行爲參數化

目前,你須要一種比添加不少參數更好的方法來應對變化的需求。讓咱們退一步來看看更高層次的抽象。一種可能解決方案是對你的懸着標準建模:你考慮的是蘋果,須要根據Apple的某些屬性(好比它是綠色的嗎?重量超過150克嗎?)來返回一個boolean值。是的,你可能已經想到了第一章中介紹到了的謂詞。ui

根據謂詞進行篩選spa

首先,咱們應該定義一個接口來對選擇標準建模:

public interface ApplePredicate {
    /**
     * 根據給定的參數計算此謂詞。
     *
     * @param apple
     * @return
     */
    boolean test(Apple apple);
}
複製代碼

能夠用ApplePredicate的實現類來表明不一樣的選擇標準:

只篩選綠蘋果

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;
    }
}
複製代碼

你能夠把這些標準看做filter的不一樣行爲。這就像策略設計模式同樣,它讓你定義一組方法,把它們封裝起來,而後在運行時選擇一個方法。這裏,方法就是ApplePredicate,不一樣的策略就是AppleHeavyWeightPredicate和AppleGreenColorPredicate。

你能夠將filterApples方法接受一個ApplePredicate對象,對Apple作條件測試。這樣就是行爲參數化:讓方法接受多種行爲做爲參數,並在內部使用,來完成不一樣的行爲。

根據抽象條件篩選

private static List<Apple> filterApples(List<Apple> apples, ApplePredicate<Apple> applePredicate) {
    List<Apple> appleList = new ArrayList<>();
    for (Apple apple : apples) {
        if (applePredicate.test(apple)) {
            appleList.add(apple);
        }
    }
    return appleList;
}
複製代碼

代碼的傳遞/行爲

酷,這段代碼看起來不少了,讀起來、用起來也更容易!如今你能夠建立不一樣的ApplePredicate對象,並將它們傳遞給filterApples方法。這樣就能夠根據不一樣的條件來建立一個類而且實現ApplePredicate就能夠了。

如今,農民要求須要篩選紅蘋果。那麼,咱們就能夠根據條件建立一個類而且實現ApplePredicate:

public class AppleRedAndHeavyPredicate implements ApplePredicate {
    @Override
    public boolean test(Apple apple) {
        return "red".equals(apple.getColor()) && apple.getWeight() > 150;
    }
}
複製代碼
List<Apple> filterApples2 = filterApples(apples, new AppleRedAndHeavyPredicate());
System.out.println("經過謂詞篩選紅蘋果而且是重蘋果:" + filterApples2);
複製代碼

酷,如今filterApples方法的行爲已經取決於經過ApplePredicate對象來實現了。這就是行爲參數化了!

可是,你有沒有發現,咱們每次新增一個條件就須要新增一個類。這樣作有點太過於麻煩,或許咱們能夠經過Lambda,將表達式傳遞給filterApples方法,這樣就無需定義多個ApplePredicate類,從而去掉沒必要要的代碼,並減輕工做量。

多種行爲,一個參數

行爲參數化的好處在與你能夠把迭代要篩選的集合的邏輯與對集合中每一個元素應用的行爲區分開來。這樣你就能夠重複使用同一個方法,給它不一樣的行爲來達到不一樣的目的。

爲了可以對參數化行爲運用自如,而且簡化代碼,咱們來嘗試將參數經過Lambda的方式傳遞給filterApples。

經過Lambda的方式來篩選紅蘋果:

List<Apple> filterApples3 = filterApples(apples, apple -> "red".equals(apple.getColor()));
複製代碼

經過Lambda的方式來篩選紅蘋果而且是重蘋果:

List<Apple> filterApples4 = filterApples(apples, apple -> "red".equals(apple.getColor()) && apple.getWeight() > 150);
複製代碼

是的,使用的已經仍是原來的條件,而且再也不須要根據不一樣的條件再去實現一個ApplePredicate類了,這樣極大的簡化了代碼。可是,農民又有一個需求了:「如今,不僅是須要對蘋果進行篩選了,還須要對香蕉、橘子、草莓進行篩選了。」

可是,咱們目前的代碼只可以對蘋果進行篩選而已,爲了解決這個問題,咱們能夠將類型定義爲泛型,這樣就不僅是隻能對蘋果進行篩選了。

使用謂詞

其實,咱們能夠不須要去定義謂詞,由於在Java中就一個了Predicate了,咱們可使用它而且實現咱們的功能。

定義一個泛型的filter方法:

private static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
    List<T> result = new ArrayList<>();
    for (T t : list) {
        if (predicate.test(t)) {
            result.add(t);
        }
    }
    return result;
}
複製代碼

這個方法是一個通用的篩選方法,不僅是能夠用於篩選蘋果。

篩選重蘋果:

List<Apple> heavyApples = filter(apples, (Apple apple) -> apple.getWeight() > 150);
複製代碼

篩選能被2整除的數:

List<Integer> numbers = Arrays.asList(10, 11, 8, 5, 1, 2, 29, 18);
List<Integer> integerList = filter(numbers, number -> number % 2 == 0);
複製代碼

是否是很酷?如今的代碼簡潔性和靈活性都很高,在Java8以前這些都是不可能作到的!

如今,你已經感受到了行爲參數化是一個頗有用的模式,它可以輕鬆地適應不斷變化的需求。在Java中不少方法均可以用不一樣的行爲來參數化,好比使用Comparator排序,用Runnable執行一個代碼塊等等。

使用Comparator來排序:

apples.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
複製代碼

或者這樣:

apples.sort(Comparator.comparing(Apple::getWeight));
複製代碼

使用Runnable執行某個代碼塊:

Thread t = new Thread(() -> System.out.println("HelloWorld"));
複製代碼

總結

  1. 行爲參數化,就是一個方法接受多個不一樣的行爲做爲參數,並在內部使用它們,完成不一樣行爲的能力。
  2. 行爲參數化可讓代碼更好的適應不斷變化的要求,減輕工做量。
  3. 傳遞代碼,就是將新行爲做爲參數傳遞給方法。但在Java8以前這實現起來很囉嗦。爲接口生命許多隻是用一次的實體類而形成的囉嗦代碼,在Java8以前採用匿名類來減小。
  4. JavaAPI包含了不少能夠用不一樣行爲進行參數化的方法,包括排序、線程等。

代碼示例:

Github:chap2

公衆號

相關文章
相關標籤/搜索