Java 8 (1) 行爲參數化

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

 

應對不斷變化的需求算法

1.第一次嘗試:實現一個功能,從一個列表中篩選出綠色蘋果的功能。設計模式

首先準備Apple實體類app

public class Apple {
    private Integer Id;
    private String Color;
    private Double Weight;

    //getter..setter..toString..
}

編寫過濾出綠色蘋果的功能ide

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

    }

輸出結果:spa

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

如今只須要這樣調用,產品就會滿意了。code

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 包含不少能夠用不一樣行爲進行參數化的方法、包括排序、線程等。

相關文章
相關標籤/搜索