在軟件工程中,一個衆所周知的問題就是,無論作什麼,用戶的需求確定會變。
如何應對這樣不斷變化的需求?理想的狀態下,應該把的工做量降到最少。此外,相似的新功能實現起來還應該很簡單,並且易於長期維護。
行爲參數化就是能夠幫助處理頻繁變動的需求的一種軟件開發模式。一言以蔽之,它意味着拿出一個代碼塊,把它準備好卻不去執行它。這個代碼塊之後能夠被程序的其餘部分調用,這意味着能夠推遲這塊代碼的執行。
以篩選蘋果爲例,逐步改進代碼,來展現一些讓代碼更靈活的最佳作法。
需求:篩選綠色蘋果java
List<Apple> inventory = Arrays.asList(new Apple(80, "green"), new Apple(155, "green"), new Apple(120, "red")); public class Apple { private int weight; private String color;
// get setter ...
}
List<Apple> apples = filterGreenApples(inventory); 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> apples = filterApplesByColor(inventory,"red"); 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; }
需求變化:刷選顏色加劇量設計模式
public static List<Apple> filterApples(List<Apple> inventory, String color,int weight) { //...
這個解決方案仍是不能很好地應對變化的需求。app
List<Apple> apples = filterApples(inventory, new AppleColorPredicate()); 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; } interface ApplePredicate { public boolean test(Apple a); } static class AppleWeightPredicate implements ApplePredicate { public boolean test(Apple apple) { return apple.getWeight() > 150; } } static class AppleColorPredicate implements ApplePredicate { public boolean test(Apple apple) { return "green".equals(apple.getColor()); } }
附:剛作的這些和「策略設計模式」相關,它讓定義一族算法,把它們封裝起來(稱爲「策略」),而後在運行時選擇一個算法。在這裏,算法族就是ApplePredicate,不一樣的策略就是AppleHeavyWeightPredicate和AppleGreenColorPredicate。 可是,該怎麼利用ApplePredicate的不一樣實現呢?須要filterApples方法接受ApplePredicate對象,對Apple作條件測試。這就是行爲參數化:讓方法接受多種行爲(或戰略)做爲參數,並在內部使用,來完成不一樣的行爲。 要在咱們的例子中實現這一點,要給filterApples方法添加一個參數,讓它接受ApplePredicate對象。這在軟件工程上有很大好處:如今把filterApples方法迭代集合的邏輯與要應用到集合中每一個元素的行爲區分開了。測試
請注意,在這個例子中,惟一重要的代碼是test方法的實現;正是它定義了filterApples方法的新行爲。因爲該filterApples方法只能接受對象,因此必須把代碼包裹在ApplePredicate對象裏。的作法就相似於在內聯「傳遞代碼」,由於是經過一個實現了test方法的對象來傳遞布爾表達式的。spa
這種行爲參數化的好處在於能夠把迭代要篩選的集合的邏輯與對集合中每一個元素應用的行爲區分開來。這樣能夠重複使用同一個方法,給它不一樣的行爲來達到不一樣的目的設計
List<Apple> redApples = filterApples(inventory, new ApplePredicate() { // 直接內聯參數化filterapples方法的行爲 public boolean test(Apple apple) { return "red".equals(apple.getColor()); } });
附:匿名類和熟悉的Java局部類(塊中定義的類)差很少,但匿名類沒有名字。它容許同時聲明並實例化一個類。換句話說,它容許隨用隨建。code
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) { //引入類型參數T List<T> result = new ArrayList<>(); for (T e : list) { if (p.test(e)) { result.add(e); } } return result; }
List<Apple> redApples = filter(inventory, (Apple apple) -> "red".equals(apple.getColor())); System.out.println(redApples); List<Integer> numbers = new ArrayList<>(); numbers.add(1); numbers.add(2); List<Integer> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0); System.out.println(evenNumbers);
參考:java8實戰第二章blog