標籤:多個 頻繁 集合排序 接受 使用 用戶端 大於 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) 行為參數化