基礎概念
在軟件工程中,一個眾所周知的問題是,不管你做什么,用戶的需求肯定會變。行為參數化就是可以幫助你處理頻繁變更的需求的一種軟件開發模式。一言以蔽之,它意味著拿出一個代碼塊,把它準備好卻不去執行它,這個代碼塊以后可以被你程序的其它部分調用,這意味著你可以推遲這塊代碼的執行。
代碼的不斷演化
最初的樣子:
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;
}
這段代碼的作用不言而喻,篩選出綠色的蘋果集,一旦顏色有所變更,就要重復的寫一段類似的代碼,在需求顏色相對較多的時候明顯不切實際這樣寫的話,一個良好的原則是在編寫類似的代碼之后,嘗試將其抽象化。
把顏色作為參數之后:
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) ) {
result.add(apple);
}
}
return result;
}
現在,只要像下面這樣調用方法,農民朋友就會滿意了:
List<Apple> greenApples = filterApplesByColor(inventory, "green");
List<Apple> redApples = filterApplesByColor(inventory, "red");
取一種顏色的時候,只需要傳對應衍射參數就可以了,如果要取兩種或者兩種以上的顏色時,再將集合取一個并集就可以了。但很明顯的是,蘋果的判斷屬性可能不止顏色這一個,例如重量,產地等。
對你能想到的每個屬性做篩選:
public static List<Apple> filterApples(List<Apple> inventory, String color,int weight, int flag) {
List<Apple> result = new ArrayList<Apple>();
for (Apple apple: inventory){
if ( (flag == 1 && apple.getColor().equals(color)) || (flag == 2 && apple.getWeight() > weight)){
result.add(apple);
}
}
return result;
}
你可以這么用(但真的很笨拙):
List<Apple> greenApples = filterApples(inventory, "green", 0, 1);
List<Apple> heavyApples = filterApples(inventory, "", 150, 2);
去最終結果就是集合之間的處理了,此處不再闡述。這個解決方案再差不過了。首先,客戶端代碼看上去糟透了。 1 和 2 是什么意思?此外,這個解決方案還是不能很好地應對變化的需求。如果這位農民要求你對蘋果的不同屬性做篩選,比如大小、形狀、產地等,又怎么辦?而且,如果農民要求你組合屬性,做更復雜的查詢,比如綠色的重蘋果,又該怎么辦?你會有好多個重復的 filter 方法,或一個巨大的非常復雜的方法。
行為化參數:
接口
public interface ApplePredicate{
boolean test (Apple apple);
}
針對接口的不同實現
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());
}
}
你可以把這些標準看做篩選的不同標準,這些和“策略設計模式”相關,它讓你定義一族算法,把它們封裝起來(稱為“策略”),然后在運行時選擇一個算法。這就是行為參數化,讓方法接受多種行為(或策略)作為參數,并在內部使用,來完成不同的行為。它的調用代碼如下:
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;
}
比如,如果農民讓你找出所有重量超過150克的紅蘋果,你只需要創建一個類來實現 ApplePredicate 就行了。 filterApples 方法的行為取決于你通過 ApplePredicate 對象傳遞的代碼。
對付啰嗦
我們都知道,人們都不愿意用那些很麻煩的功能或概念。目前,當要把新的行為傳遞給filterApples 方法的時候,你不得不聲明好幾個實現ApplePredicate 接口的類,然后實例化好幾個只會提到一次的 ApplePredicate 對象。
使用匿名類:
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
public boolean test(Apple apple){
return "red".equals(apple.getColor());
}
});
此時,根據篩選條件的不同,在匿名類中的實現也就不同。但是匿名類有兩個明顯的缺陷:1.占用了較多的空間;2.很多程序員覺得它用起來讓人費解,也就是不好理解的意思。整體來說,啰 嗦就不好;它讓人不愿意使用語言的某種功能,因為編寫和維護 啰 嗦的代碼需要很長時間,而且代碼也不易讀。好的代碼應該是一目了然的。即使匿名類處理在某種程度上改善了為一個接口聲明好幾個實體類的 啰 嗦問題,但它仍不能令人滿意。
在理想的情況下,我們想鼓勵程序員使用行為參數化模式,因為正如你在前面看到的,它讓代碼更能適應需求的變化。在后面會詳細介紹的,使用 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;
}
現在你可以把 filter 方法用在香蕉、桔子、 Integer 或是 String 的列表上了,下面是一些真實使用的例子:
1.用 Comparator 來排序
匿名類時的代碼:
inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
Lambda表達式:
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
2.用Runnable執行代碼塊
匿名類時的代碼:
Thread t = new Thread(new Runnable() {
public void run(){
System.out.println("Hello world");
}
});
Lambda表達式:
Thread t = new Thread(() -> System.out.println("Hello world"));
小結
- 行為參數化,就是一個方法接受多個不同的行為作為參數,并在內部使用它們,完成不同行為的能力。
- 行為參數化可讓代碼更好地適應不斷變化的要求,減輕未來的工作量。
- 傳遞代碼,就是將新行為作為參數傳遞給方法。但在Java 8之前這實現起來很啰嗦。為接口聲明許多只用一次的實體類而造成的啰嗦代碼,在Java 8之前可以用匿名類來減少。
- Java API包含很多可以用不同行為進行參數化的方法,包括排序、線程和GUI處理。