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