第一個解決方案多是下面這樣的:程序員
public static List<Apple> filterGreenApples(List<Apple> inventory) {
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory) {
if("green".equals(apple.getColor())) {// 篩選蘋果的條件
result.add(apple);
}
}
return result;
}
複製代碼
可是如今還想要篩選紅蘋果,你該怎麼作呢?簡單的解決方法就是複製這個方法,把名字改成filterRedApple,而後更改if條件來匹配紅蘋果。而後,要是還想篩選多種顏色:淺綠色、暗紅色、黃色等,這種方法就應付不了了。一個良好的原則是在編寫相似的代碼以後,嘗試將其抽象化。設計模式
一種作法是給方法加一個參數,把顏色變成參數,這樣就能靈活地適應變化了:bash
public static List<Apple> filterApplesByColor(List<Apple> inventory,
String color) {
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory) {
if(apple.getColor().equals(color)) {// 篩選蘋果的條件
result.add(apple);
}
}
return result;
}
複製代碼
可是如今還想篩選重的蘋果(質量大於150g的蘋果),因而你寫了下面的方法,用另外一個參數來應對不一樣的重量:app
public static List<Apple> filterApplesByWeight(List<Apple> inventory,
int weight) {
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory) {
if(apple.getWeight() > weight) {// 篩選蘋果的條件
result.add(apple);
}
}
return result;
}
複製代碼
解決方案不錯,可是請注意,你複製了大部分的代碼來實現便利庫存,並對每一個蘋果應用篩選條件。這有點兒使人失望,由於它打破了DRY(Don't Repeat Yourself,不要重複本身)的軟件工程原則。若是你想要改變篩選遍歷方式來提高性能呢?那就得修改全部方法得實現,而不是隻改一個。從工程工做量的角度來看,這代價太大了。 你能夠將顏色和重量結合爲一個方法,成爲filter。不過就算這樣,你還須要一種方式來區分想要篩選哪一個屬性。函數
一種把全部屬性結合起來的笨拙嘗試以下所示:性能
public static List<Apple> filterApples(List<Apple> inventory,
String color, int weight, boolean flag) {
List<Apple> result = new ArrayList<>();
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是什麼意思?此外,這個解決方案不能很好地應對變化的需求。若是要求你對蘋果的不一樣屬性作篩選,好比大小,形狀,產地等,又怎麼辦?並且,若是要求組合屬性篩選,作更復雜的查詢,好比綠色的重蘋果,又該怎麼辦?但現在這種狀況下,你須要一種更好的方式,來把蘋果的選擇標準告訴你得filterApples方法。 讓咱們後退一步來看看更高層次的抽象。一種可能的解決方案是對你的選擇標準建模:你考慮的是蘋果,須要根據Apple的某些屬性來返回一個Boolean值。咱們把它稱爲謂詞(即一個返回Boolean值的函數)。讓咱們定義一個接口來對選擇標準建模:ui
public interface ApplePredicate {
boolean test(Apple apple);
}
複製代碼
如今你就能夠用ApplePredicate的多個實現表明不一樣的選擇標準了,好比:spa
// 篩選重量大於150的蘋果的謂詞
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 "green".equals(apple.getColor());
}
}
複製代碼
可是,該怎麼利用ApplePredicate呢?你須要filterApples方法接受ApplePredicate對象,對Apple作條件測試。這就是行爲參數化:讓方法接受多種行爲(或策略)做爲參數,並在內部使用,來完成不一樣的行爲。設計
利用ApplePredicate改過以後,filter方法看起來是這樣的:
public static List<Apple> filterApples(List<Apple> inventory,
ApplePredicate p) {
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory) {
if(p.test(apple)) {
result.add(apple);
}
}
return result;
}
複製代碼
你已經作成了一件很酷的事:filterApples方法取決於你經過ApplePredicate對象傳遞的代碼。換句話說,你把filterApples方法的行爲參數化了! 請注意,在上一個例子中,惟一重要的代碼事test方法的實現,正是它定義了filterApples方法的新行爲。但使人遺憾的是,因爲該filterApples方法只能接受對象,因此你必須把代碼包裹在ApplePredicate對象裏。你的作法就相似於在內聯」傳遞代碼「,由於你是經過一個實現了test方法的對象來傳遞布爾表達式的。
public class AppleHeavyWeightPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
複製代碼
把策略傳遞給策略方法:經過不二表達式篩選封裝在ApplePredicate對象內的蘋果。爲了封裝這段代碼,用了不少模板代碼來包裹它(粗體顯示)
咱們都知道,人們都不肯意用那些很麻煩的功能或概念。目前,當要把新的行爲傳遞給filterApples方法的時候,你不得不聲明好幾個實現ApplePredicate接口的類,而後實例化好幾個只會提到一次的ApplePredicate對象。下面的程序總結了你目前看到的一切。這真是很囉嗦,很費時間。 費那麼大勁兒真不必,能不能作的更好呢?Java有一個機制稱爲匿名類,它可讓你同時聲明和實例化一個類。它能夠幫助你進一步改善代碼,讓他變得更簡潔。但這也不徹底使人滿意。 匿名類:匿名類和你熟悉的Java局部類(塊中定義的類)差很少,但匿名類沒有名字。它容許你同時聲明並實例化一個類。換句話說,它容許你隨用隨建。
下面的代碼展現瞭如何經過建立一個匿名類實現ApplePredicate的對象,重寫篩選的例子:
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
public boolean test(Apple apple) {
return "red".equals(apple.getColor());
}
})
複製代碼
GUI應用程序中常用匿名類來建立事件處理器對象(下面的例子使用的是Java FX API,一種現代的Java UI平臺)
buuton.setOnAction(new EventHandler<ActionEvent>() {
public void handler(ActionEvent event) {
System.out.println("Woooo a click!");
}
});
複製代碼
但匿名類仍是不夠好。第一,它每每很笨重,由於它佔用了不少空間。還拿前面的例子來看,以下面加粗代碼所示:
List<Apple> redApples = filterApples(inventory, new ApplePredicate(){
public boolean test(Apple a){
return "red".equals(a.getColor());
}
});
buuton.setOnAction(new EventHandler<ActionEvent>() {
public void handler(ActionEvent event) {
System.out.println("Woooo a click!");
}
});
複製代碼
第二,不少程序員以爲它用起來很讓人費解
上面的代碼在Java8裏能夠用Lambda表達式重寫爲下面的樣子:
List<Apple> result =
filterApples(inventory, (Apple apple) -> "red".equals(apple.getColor()));
複製代碼
不得不認可這代碼看上去比先前乾淨不少。這很好,由於它看起來更像問題陳述自己了。咱們已經解決了囉嗦的問題。
在通往抽象的路上,咱們還能夠更進一步。目前,filterApples方法還只適用於Apple。你還能夠將List類型抽象化,從而超越你眼前要處理的問題:
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;
}
複製代碼
你如今已經看到,行爲參數化是一個頗有用的模式,它可以輕鬆地適應不斷變化的需求。這種模式能夠把一個行爲(一段代碼)封裝起來,並經過傳遞和使用建立的行爲(例如對Apple的不一樣謂詞)將方法的行爲參數化。前面提到過,這種作法相似於策略設計模式。你可能已經在實踐中用過這個模式了。Java API中的不少方法均可以用不一樣的行爲來參數化。這種方法每每與匿名類一塊兒使用。咱們會展現三個例子,這應該能幫助你鞏固傳遞代碼的思想:用一個Comparator排序,用Runnable執行一個代碼塊,以及GUI事件處理。