1. 程式人生 > >java1.8實戰學習(三)——通過行為引數化傳遞程式碼

java1.8實戰學習(三)——通過行為引數化傳遞程式碼

 上一篇:java1.8實戰學習(二)——總結:流處理、行為引數化、並行與共享

 下一篇:java1.8實戰學習(四)——通過行為引數化傳遞程式碼

  • 通過行為引數化傳遞程式碼

在軟體工程中,一個眾所周知的問題就是,不管你做什麼,使用者的需求肯定會變。比方說,有個應用程式是幫助農民瞭解自己的庫存的。這位農民可能想有一個查詢庫存中所有綠色蘋果的功能。但到了第二天,他可能會告訴你:“其實我還想找出所有重量超過150克的蘋果。”又過了兩天,農民又跑回來補充道:“要是我可以找出所有既是綠色,重量也超過150克的蘋果,那就太棒了。”你要如何應對這樣不斷變化的需求?理想的狀態下,應該把你的工作量降到最少。此外,類似的新功能實現起來還應該很簡單,而且易於長期維護。

行為引數化就是可以幫助你處理頻繁變更的需求的一種軟體開發模式。一言以蔽之,它意味著拿出一個程式碼塊,把它準備好卻不去執行它。這個程式碼塊以後可以被你程式的其他部分呼叫,這意味著你可以推遲這塊程式碼的執行。例如,你可以將程式碼塊作為引數傳遞給另一個方法,稍後再去執行它。這樣,這個方法的行為就基於那塊程式碼被引數化了。例如,如果你要處理一個集合,可能會寫一個方法:

可以對列表中的每個元素做“某件事”

可以在列表處理完後做“另一件事”

遇到錯誤時可以做“另外一件事”

  • 應對不斷變化的需求

初試牛刀:篩選綠蘋果

第一個解決方案可能是這樣:

public static List<Apple> filterGreenApples(List<Apple> inventory) { 
 List<Apple> result = new ArrayList<Apple>(); 
 for(Apple apple: inventory){ 
 if( "green".equals(apple.getColor() ) { //僅僅是選出綠蘋果
 result.add(apple); 
 } 
 } 
 return result; 
} 

但是現在農民改主意了,他還想要篩選紅蘋果。你該怎麼做呢?簡單的解決辦法就是複製這個方法,把名字改成filterRedApples,然後更改if條件來匹配紅蘋果。然而,要是農民想要篩選多種顏色:淺綠色、暗紅色、黃色等,這種方法就應付不了了。一個良好的原則是在編寫類似的程式碼之後,嘗試將其抽象化。

再展身手:把顏色作為引數

一種做法是給方法加一個引數,把顏色變成引數,這樣就能靈活地適應變化了:

public static List<Apple> filterApplesByColor(List<Apple> inventory, String color) { 
 List<Apple> result = new ArrayList<Apple>(); 
 for (Apple apple: inventory){ 
 if ( apple.getColor().equals(color) ) { //根據引數color來選取蘋果
 result.add(apple); 
 } 
 } 
 return result; 
} 

現在,只要像下面這樣呼叫方法,農民朋友就會滿意了: 

List<Apple> greenApples = filterApplesByColor(inventory, "green"); 
List<Apple> redApples = filterApplesByColor(inventory, "red"); 

 這位農民又跑回來和你說:“要是能區分輕的蘋果和重的蘋果就太好了。重的蘋果一般是重量大於150克。”作為軟體工程師,你早就想到農民可能會要改變重量,於是你寫了下面的方法,用另一個引數來應對不同的重量:

public static List<Apple> filterApplesByWeight(List<Apple> inventory,  int weight) { 
 List<Apple> result = new ArrayList<Apple>(); 
 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<Apple>(); 
 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是什麼意思?此外,這個解決方案還是不能很好地應對變化的需求。如果這位農民要求你對蘋果的不同屬性做篩選,比如大小、形狀、產地等,又怎麼辦?而且,如果農民要求你組合屬性,做更復雜的查詢,比如綠色的重蘋果,又該怎麼辦?你會有好多個重複的filter方法,或一個巨大的非常複雜的方法。到目前為止,你已經給filterApples方法加上了值(比如String、Integer或boolean)的引數。這對於某些確定性問題可能還不錯。但如今這種情況下,你需要一種更好的方式,來把蘋果的選擇標準告訴你的filterApples方法。

在下一節中,我們會介紹瞭如何利用行為引數化實現這種靈活性。