JAVA函數式編程(1)- 行為參數化

什么是行為參數化?

行為參數化就是可以幫助你處理頻繁變更的需求的一種軟件開發方式。它意味著拿出一個代碼塊,把它準備好卻不去執行它。這個代碼塊以后可以被你的程序其他部分調用,你也可以推遲你的這塊代碼的執行。例如,你可以把這塊代碼作為參數傳遞給另一個方法,稍后再去執行它。這樣,這個方法的行為就基于那塊代碼被參數化了。

行為參數化使我們的代碼更靈活

讓我一個簡單的例子來說明一些具體的做法,看我們是如何通過行為參數化讓我們的代碼越來越靈活。現在假如我們面對這樣一個問題:從一個列表中篩選出顏色是綠色的蘋果。

篩選綠蘋果

按照我們的傳統思維,第一個解決方案可能是這樣的:

private static List<Apple> filterGreenApples(List<Apple> inventory) {
    List<Apple> result = new ArrayList<Apple>();
    for (Apple apple:inventory){
        if ("green".equals(apple.getColor())){
            result.add(apple);
        }
    }
    return result;
}

現在我們的問題改變了,還想篩選紅蘋果,我們最簡單的做法就是復制這個方法,把名字改成filterRedApples,然后更改if條件來匹配紅蘋果。然而,要是我們想選多種顏色:淺綠色、暗紅色、黃色等,這種方法就應付不了了。一個良好的原則就是在編寫類似的代碼之后,嘗試將其抽象化。

將顏色作為參數

我們現在把顏色變成一個傳入參數,就可以靈活地適應以上的需求變化了。

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

現在只需傳入不同的顏色參數,我們的方法就能為我們篩選出對應顏色的蘋果。

List<Apple> greenApples = filterApplesByColor(inventory, "green");
List<Apple> redApples = filterApplesByColor(inventory, "red");

太簡單了對吧?此時我們的需求又變了,我們除了要篩選蘋果的顏色外我們還需要篩選蘋果的輕重,我們需要找出重量大于150g的蘋果。于是你寫下了下面的方法,用一個重量參數來作為比較的對象。

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

解決方案看上去不錯,但是請你對比一下filterApplesByWeight和filterApplesByColor兩個方法,是不是覺得他們太相似了?我們寫了大量重復的代碼來實現遍歷的工作,真正有區別的僅僅是應用篩選條件的那一行,這和我們代碼整潔的原則是相違背的,因為我們在重復。現在你可以將顏色和重量結合為一個方法,稱為filter,然后通過一個標志位來區分對顏色和重量的查詢,于是我們的代碼變成了這樣。

加入標志位,對不同的屬性進行篩選

又一個看似靈活卻很丑陋的方法誕生了:

public static List<Apple> filterApples(List<Apple> inventory, String color, Integer weight, Boolean flag) {
    List<Apple> result = new ArrayList<Apple>();
    for (Apple apple : inventory) {
        if ((flag && apple.getColor().equals(color)) ||
                (!flag && apple.getWeight() > weight)) {
            result.add(apple);
        }
    }
    return result;
}

這個方法確實太丑了,首先從我們調用它的代碼就看的出來:

List<Apple> greenApples = filterApples(inventory, "green", 0, true);
List<Apple> heavyApples = filterApples(inventory, "", 150, false);

這兩行代碼可讀性極差,true和false分別代表什么意思?只有查看實現才能明白,而且我們的color和weight參數總有一個是沒有意義的,更重要的是即便如此我們的方法仍然無法很好地應對變化的需求。現在如果我們的需求變為對蘋果的不同屬性做篩選,比如大小、形狀、顏色、產地等,又應該怎么辦?而且如果要求你組合其中的兩個甚至更多的屬性,做更復雜的查詢,又該怎么辦?你會被迫去生產更多更丑陋的filter函數。下面讓我們來看看如何利用行為參數化的方法來使我們的filter方法更靈活、更優雅。

行為參數化

讓我們來看看更高層次的抽象,一種可能的解決方案是對你的選擇標準進行建模:你考慮的是蘋果,需要根據Apple的某些屬性來返回一個布爾值,于是我們設計了這樣一個接口:

public interface ApplePredicate {
    Boolean test(Apple apple);
}

我們給這個接口添加兩個實現,分別用于篩選重量大于150g和顏色是綠色的蘋果。

public class AppleHeavyWeightPredicate implements ApplePredicate {
    public Boolean test(Apple apple) {
        return apple.getWeight() > 150;
    }
}

public class AppleGreenColorPredicate implements ApplePredicate{
    public Boolean test(Apple apple) {
        return apple.getColor().equals("green");
    }
}

現在你可以通過AppleHeavyWeightPredicate和AppleGreenColorPredicate這兩個實現類代表不同的選擇標準了,你可以把這些標準看作filter方法的不同行為,通過傳入不同的標準使得filter的行為發生改變。
利用ApplePredicate過后filter方法變成了這樣:

public static List<Apple> filterApples(List<Apple> inventory , ApplePredicate predicate){
    List<Apple> result = new ArrayList<Apple>();
    for (Apple apple : inventory) {
        if (predicate.test(apple)){
            result.add(apple);
        }
    }
    return result;
}

現在你只需要讓你的filterApple方法接受ApplePredicate接口的對象,就可以對Apple做篩選的測試了,而你的filterApple方法將不會隨之改變!因為你把filterApples方法迭代集合的邏輯與你想要應用到集合中每個元素的行為分開了,這就是行為參數化:

讓方法接受多種行為作為參數,并在內部使用,來完成不同的行為。

目前為止,我們已經讓我們的代碼變得優雅了許多,但是我們仍然面臨著一個問題:每當篩選的條件變更時,我們需要實現一個新的ApplePredicate類來定義篩選的行為,這樣會不會太啰嗦了?答案是肯定的,并且這種啰嗦是不必要的。

對付啰嗦

我們都知道java有一個機制成為匿名類,讓你可以同時聲明和示例化一個類。它可以讓我們進一步改善我們的代碼,讓它變得更簡潔。

使用匿名類

下面的代碼展示了如何通過創建一個匿名類實現ApplePredicate對象。

List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
    public Boolean test(Apple apple) {
        return"red".equals(apple.getColor());
    }
});

好的代碼應該是一目了然的,即使匿名類的出現在某種程度上改善了為一個接口聲明好幾個實體類的啰嗦問題,但它仍然不能令人滿意:

  1. 它還是很笨重,包括new語句在內,還有一堆模版化的代碼存在。
  2. 它的可讀性還是很差,很多程序員覺得它看上去令人費解。

我們的業務場景中只需要傳遞一段簡單的代碼,那就是if語句所判定的條件,但在使用匿名類時仍然需要創建一個接口的對象,明確地實現一個方法來定義一個新的行為。
那有沒有更加簡潔的方法來傳遞這一段代碼呢?Java8中引入的Lambda表達式就恰好完美的解決了這一問題。

使用Lambda表達式

上面的代碼在Java8里可以使用Lambda表達式寫為下面的樣子:

List<Apple> redApples = 
       filterApples(inventory, (Apple apple) -> "red".equals(apple.getColor()));

是不是這行代碼比之前的匿名類版本代碼簡潔了許多?因為它看起來更像是問題陳述本身了,并且它不需要聲明更多的模版代碼,在解決了啰嗦的同時可讀性也大大提高了。
當然我們并不只滿足于篩選“紅蘋果”這一簡單的抽象,我們還可以更加進一步的將test方法給抽象成范型版本,從而超越蘋果的概念,用于處理任意的類型。

終極目標,萬能的filter方法

將我們的Predicate接口和filter方法都范型處理:

//范型版Predicate
public interface Predicate<T> {
    Boolean test(T t);
}

//范型版filter
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
    List<T> result = new ArrayList<T>();
    for (T e : list) {
        if (predicate.test(e)) {
            result.add(e);
        }
    }
    return result;
}

現在你可以將filter方法用在任意你所想要篩選的對象列表上了:

//篩選小橙子
List<Orange> smallOranges =
        filter(inventory, (Orange orange) -> "orange.getSize()<=10");
//篩選偶數
List<Integer> evenNumbers =
        filter(numbers, (Integer i) -> i % 2 == 0);

現在我們終于在靈活性和簡潔性之間找到了最佳平衡點,不是嗎?而這一切都得益于Lambda函數的強大威力,我們將在今后的章節中專門介紹Lambda函數的特性,敬請期待。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 第一章 為什么要關心Java 8 使用Stream庫來選擇最佳低級執行機制可以避免使用Synchronized(同...
    謝隨安閱讀 1,508評論 0 4
  • 前言 《Java8實戰》不得不說是一本好書,捧起來看起來就興奮得不想放下,其中介紹的函數式編程實在是太令人興奮了,...
    我沒有三顆心臟閱讀 2,075評論 1 0
  • 原文鏈接:https://github.com/EasyKotlin 值就是函數,函數就是值。所有函數都消費函數,...
    JackChen1024閱讀 6,027評論 1 17
  • 如何應對不斷變化的需求 軟件工程中,用戶的需求肯定會變,如何應對不斷變化的需求,理想狀態下,應該把工作量降到最少,...
    小Noodles閱讀 1,333評論 0 0
  • 本來想在達哈卜多呆些日子,系統學下潛水,在這個安靜悠閑的小鎮子吃飯打豆豆。 但是對于埃及,實在有太多想去探索,只好...
    FabulousGrace閱讀 867評論 0 0