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

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

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

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

行為引數化

在上一節中已經看到了,你需要一種比新增很多引數更好的方法來應對變化的需求。讓我們後退一步來看看更高層次的抽象。一種可能的解決方案是對你的選擇標準建模:你考慮的是蘋果,需要根據Apple的某些屬性(比如它是綠色的嗎?重量超過150克嗎?)來返回一個boolean值。我們把它稱為謂詞(即一個返回boolean值的函式)。讓我們定義一個介面來對選擇標準建模

public interface ApplePredicate{ 
 boolean test (Apple apple); 
} 

現在你就可以用ApplePredicate的多個實現代表不同的選擇標準了,比如(如圖2-1所示):

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,不同的策略就是AppleHeavyWeightPredicate和AppleGreenColorPredicate。但是,該怎麼利用ApplePredicate的不同實現呢?你需要filterApples方法接受ApplePredicate物件,對Apple做條件測試。

這就是行為引數化:讓方法接受多種行為(或戰略)作為引數,並在內部使用,來完成不同的行為。

要在我們的例子中實現這一點,你要給filterApples方法新增一個引數,讓它接受ApplePredicate物件。這在軟體工程上有很大好處:現在你把filterApples方法迭代集合的邏輯與你要應用到集合中每個元素的行為(這裡是一個謂詞)區分開了。

 

第四次嘗試:根據抽象條件篩選

利用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; 
} 

1. 傳遞程式碼/行為

這段程式碼比我們第一次嘗試的時候靈活多了,讀起來、用起來也更容易!現在你可以建立不同的ApplePredicate物件,並將它們傳遞給filterApples方法。比如,如果農民讓你找出所有重量超過150克的紅蘋果,你只需要建立一個類來實ApplePredicate就行了。你的程式碼現在足夠靈活,可以應對任何涉及蘋果屬性的需求變更了:

public class AppleRedAndHeavyPredicate implements ApplePredicate{ 
 public boolean test(Apple apple){ 
 return "red".equals(apple.getColor()) 
 && apple.getWeight() > 150; 
 } 
} 
List<Apple> redAndHeavyApples = 
 filterApples(inventory, new AppleRedAndHeavyPredicate()); 

filterApples方法的行為取決於你通過ApplePredicate物件傳遞的程式碼。換句話說,你把filterApples方法的行為引數化了!

 請注意,在上一個例子中,唯一重要的程式碼是test方法的實現,如圖2-2所示;正是它定義了filterApples方法的新行為。但令人遺憾的是,由於該filterApples方法只能接受物件,所以你必須把程式碼包裹在ApplePredicate物件裡。你的做法就類似於在內聯“傳遞程式碼”,因為你是通過一個實現了test方法的物件來傳遞布林表示式的。你將在後面看到,通過使用Lambda,你可以直接把表示式"red".equals(apple.getColor()) &&apple.getWeight() > 150傳遞給filterApples方法,而無需定義多個ApplePredicate類,從而去掉不必要的程式碼。

 

2. 多種行為,一個引數

行為引數化的好處在於你可以把迭代要篩選的集合的邏輯與對集合中每個元素應用的行為區分開來。這樣你可以重複使用同一個方法,給它不同的行為來達到不同的目的,如圖2-3所示。

一個測驗例子

編寫一個prettyPrintApple方法,它接受一個Apple的List,並可以對它引數化,以多種方式根據蘋果生成一個String輸出(有點兒像多個可定製的toString方法)。例如,你可以告訴 prettyPrintApple 方法,只打印每個蘋果的重量。此外,你可以讓prettyPrintApple方法分別列印每個蘋果,然後說明它是重的還是輕的。解決方案和我們前面討論的篩選的例子類似。為了幫你上手,我們提供了prettyPrintApple方法的一個粗略的框架:

public static void prettyPrintApple(List<Apple> inventory, ???){ 
 for(Apple apple: inventory) { 
 String output = ???.???(apple); 
 System.out.println(output); 
 } 
} 

答案見下一節