什麼是行爲參數化
行爲參數化是使方法接受多種行爲作爲參數,並在內部使用,完成不同的行爲。行爲參數化可讓代碼更好地適應不斷變化的要求,減輕未來的工作量,是可以幫助你處理頻繁變更的需求的一種軟件開發模式。
需要指出的是,行爲參數化不是Java8的新特性,但是是Java8新特性的重要基礎和思想。
舉例解釋:假設有這樣一個場景,小明開車去超市買東西,我們可以定義一個goAndBuy()方法。但針對另一個場景,小明開車去醫院掛號或者去動物園看猩猩,使用goAndBuy方法顯然是不正確的。如果我們定義go方法,將買東西、掛號、看猩猩等不同行爲作爲參數,送給go方法執行,則能夠很好地適應新場景。
理解行爲參數化的經典案例–蘋果的故事
案例中不斷變換需求,使得不得不重構代碼應對新的變化。
1 定義蘋果對象類
public static class Apple {
private int weight; // 重量
private String color; // 顏色
public Apple(int weight, String color) {
this.weight = weight;
this.color = color;
}
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Apple{" + "weight=" + weight + ", color='" + color + '\'' + '}';
}
}
2 根據顏色篩選蘋果(值參數化)
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;
}
List<Apple> greenApples = filterGreenApples(inventory);
針對於蘋果存貨列表inventory,我們做遍歷操作,輕而易舉地獲取到綠色蘋果列表。此時,修改需求轉爲選出綠色蘋果,我們會修改上述方法:
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;
}
List<Apple> redApples = filterApplesByColor(inventory, "red");
通過添加color形參,解決了不同顏色水果的問題。然而Apple類有多個屬性,此時需求改成按照蘋果重量篩選,以及按照蘋果顏色和重量篩選,上面的方法便不能很好的應對,也容易造成代碼的冗餘。
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;
}
List<Apple> weightBiggerThan120Apples = filterApplesByWeight(inventory, 120);
3 使用匿名內部類進行優化
顯而易見,方法代碼中存在大量重複代碼,唯一不同的便是篩選條件不同。我們將篩選這一行爲,從filterApples方法中單獨取出。
定義篩選條件接口
public interface ApplePredicate {
boolean test(Apple apple);
}
同時繼續修改過濾函數
public static List<Apple> filterApples2(List<Apple> inventory, ApplePredicate predicate) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (predicate.test(apple)) {
result.add(apple);
}
}
return result;
}
方法調用–採用的策略是綠色蘋果,重量大於150g。
List<Apple> heavyApplesByAnonyInnerClass = filterApples2(inventory, new ApplePredicate() {
@Override
public boolean test(Apple apple) {
return "green".equals(apple.color) && apple.getWeight() > 150;
}
});
定義ApplePredicate接口,作爲對每個Apple元素判斷行爲的策略規範。給filterApples方法添加一個參數,讓它接受ApplePredicate對象,這就相當於將對Apple判斷行爲test傳遞給了filterApples方法。此時,我們可以任意修改策略,來滿足不同場景的需求,而不需要變更filterApples方法。
這一過程即是行爲參數化:讓方法接受多種行爲(策略)做參數,並表現出不同目的。行爲參數化,很好的將迭代整個Apple列表的操作與針對每個Apple元素的操作做了解耦,從而能夠重複使用同一個方法,給予不同行爲參數,獲取不同目的。
行爲參數化與策略模式息息相關,定義一系列算法,將它們封裝起來,並且使他們可相互替換。本小節使用的是匿名內部類,如果傳入的是實現類,可以更加直觀的理解策略模式的使用。
4 使用Lambda表達式繼續優化
在Java8之前,方法只能接受對象。因此對Apple元素的判斷行爲代碼必須要包裹在對象(ApplePredicate對象)中傳遞給filterApples方法。同時匿名內部類雖然優於具體實現類,但仍有一些不必要代碼,並且易讀性差。
Java8開始,我們有了更好的解決方案–使用Lambda表達式,清晰易讀,代碼量少。
List<Apple> heavyApplesLambda = filterApples2(inventory,
apple -> "green".equals(apple.color) && apple.getWeight() > 150);
5 小結
值參數化中的值,包含基本類型、對象(對象引用)。Java8之前我們只能將基本類型和對象引用傳遞給方法。上方的類和匿名類代碼實現上是值參數化(傳遞類給方法),將其歸於行爲參數化,是因爲他們本身只是爲了傳遞行爲。Java8之後,
可以將方法引用和lambda代碼傳遞給方法,更加體現行爲參數化這一軟件開發模式–將代碼傳遞給方法。
參考資料
- Java8實戰
- 設計模式