Java 8 (1) 行為參數化

來源:互聯網
上載者:User

標籤:多個   頻繁   集合排序   接受   使用   用戶端   大於   string   完成   

  行為參數化就是可以協助你處理頻繁變更需求的一種軟體開發模式。它意味著拿出一個代碼塊,把它準備好卻不去執行它。這個代碼塊以後可以被你程式的其他部分調用,這意味著你可以延遲這塊代碼的執行。例如:你可以將代碼塊作為參數傳遞給另一個方法,稍後再去執行它。

 

應對不斷變化的需求

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;@FunctionalInterfacepublic 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;@FunctionalInterfacepublic 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 包含很多可以用不同行為進行參數化的方法、包括排序、線程等。

 

Java 8 (1) 行為參數化

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.