通過行為參數(shù)化傳遞代碼行為參數(shù)化在《Java8實(shí)戰(zhàn)》第二章主要介紹的是 通過行為參數(shù)化傳遞代碼 ,那么就來了解一下什么是 行為參數(shù)化 吧。 在軟件工程中,一個從所周知的問題就是,不管你做什么,用戶的需求總是會變的(PM的需求總是會變的)。比方說,有個應(yīng)用程序是幫助農(nóng)民了解自己的庫存。這位農(nóng)民可能想有一個查找?guī)齑嬷兴芯G色蘋果的功能。但是到了第二天,他突然告訴你:“其實(shí)我還想找出所有重量超過150克的蘋果?!?,你一想簡單嘛不就是改一下條件而已。于是過了兩天,他又說:“要是我可以篩選即使綠色的蘋果,重量也超過150克的蘋果?!?,這樣頻繁的改需求也不太好,面對這樣的情況理想狀態(tài)下應(yīng)該把工作量降到最低。此外,類似的功能實(shí)現(xiàn)起來應(yīng)該還是很簡單,而且利于長期維護(hù)。 行為參數(shù)化就是要幫助你處理頻繁更變的需求的一種軟件開發(fā)模式。一言以蔽之,它意味著拿出一個代碼塊,把它準(zhǔn)備好卻不去執(zhí)行它。這個代碼塊以后可以被你程序的其他部分調(diào)用,這意味著你可以推遲這塊代碼的執(zhí)行。例如,你可以將代碼塊作為參數(shù)傳遞給另外一個方法,稍后再去執(zhí)行它。這樣,這個方法的行為就基于那塊代碼被參數(shù)化了。 應(yīng)對不斷變化的需求想要編寫能應(yīng)對變化的需求并不容易。讓我們來看一個例子,我們將會逐漸的改進(jìn)這個例子,以展示一些讓代碼更靈活的做法。就像農(nóng)場庫存程序而言,你需要實(shí)現(xiàn)一個從列表中篩選綠蘋果的功能。 篩選蘋果篩選綠蘋果,可能你選擇最初的解決方案就是這樣:
private static List<Apple> filterGreenApples(List<Apple> apples) {
List<Apple> appleList = new ArrayList<Apple>();
for (Apple apple : apples) {
if ('green'.equals(apple.getColor())) {
appleList.add(apple);
}
}
return appleList;
}
現(xiàn)在代碼中就是篩選綠蘋果。但現(xiàn)在農(nóng)民改主意了,他還想要篩選紅蘋果。按照最簡單的方法就是,把方法復(fù)制一下并且改一下條件為篩選紅蘋果的條件。是的,這樣做起來很簡單,要是農(nóng)民想要篩選多種顏色:青色、深紅、淡紅...這種方法就不太適合了。 優(yōu)化代碼,通過顏色作為參數(shù)篩選蘋果:
private static List<Apple> filterApplesByColor(List<Apple> apples, String color) {
List<Apple> appleList = new ArrayList<Apple>();
for (Apple apple : apples) {
if (color.equals(apple.getColor())) {
appleList.add(apple);
}
}
return appleList;
}
很簡單對吧?,F(xiàn)在,農(nóng)民又有想法:“能篩選出輕蘋果和重蘋果就好啦!一般重蘋果的重量是150克?!蹦憧赡茉缇拖氲搅诵枰ㄟ^重量來篩選蘋果,于是你又把參數(shù)穿進(jìn)來作為條件進(jìn)行篩選。 將重量作為參數(shù),進(jìn)行重蘋果篩選:
private static List<Apple> filterApplesByWeight(List<Apple> apples, int weight) {
List<Apple> appleList = new ArrayList<Apple>();
for (Apple apple : apples) {
if (apple.getWeight() > weight) {
appleList.add(apple);
}
}
return appleList;
}
是的,解決方法很簡單,但是你復(fù)制了大部分的代碼來實(shí)現(xiàn)遍歷庫存,并對每個蘋果應(yīng)用篩選條件。這樣破壞了DRY(Don't Repeat Yourself 不要重復(fù)自己)的軟件工程原則?;蛟S,你一下就想到了這辦法,將所有的參數(shù)都放在一個方法中,這樣就可以簡化很多代碼了。 第三次嘗試,對你能想到的每個屬性做篩選:
private static List<Apple> filterApples(List<Apple> apples, String color, int weight, boolean flag) {
List<Apple> appleList = new ArrayList<Apple>();
for (Apple apple : apples) {
boolean result = (flag && apple.getWeight() > weight) || (!flag && color.equals(apple.getColor()));
if (result) {
appleList.add(apple);
}
}
return appleList;
}
代碼看起來很簡單,但是感覺卻是不太好。如果不把注釋寫清楚,別人閱讀你代碼時根本就不知道flag是干嘛用的。要是,農(nóng)民突然又有個想法,需用通過大小、形狀、產(chǎn)地等條件來進(jìn)行篩選怎么辦?所以,我們需要利用行為參數(shù)化來解決這個問題,提高代碼的靈活性。 行為參數(shù)化目前,你需要一種比添加很多參數(shù)更好的方法來應(yīng)對變化的需求。讓我們退一步來看看更高層次的抽象。一種可能解決方案是對你的懸著標(biāo)準(zhǔn)建模:你考慮的是蘋果,需要根據(jù)Apple的某些屬性(比如它是綠色的嗎?重量超過150克嗎?)來返回一個boolean值。是的,你可能已經(jīng)想到了第一章中介紹到了的謂詞。 根據(jù)謂詞進(jìn)行篩選
首先,我們應(yīng)該定義一個接口來對選擇標(biāo)準(zhǔn)建模: public interface ApplePredicate {
/**
* 根據(jù)給定的參數(shù)計(jì)算此謂詞。
*
* @param apple
* @return
*/
boolean test(Apple apple);
}
可以用ApplePredicate的實(shí)現(xiàn)類來代表不同的選擇標(biāo)準(zhǔn): 只篩選綠蘋果 public class AppleGreenColorPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return 'green'.equals(apple.getColor());
}
}
只篩選重蘋果 public class AppleHeavyWeightPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
你可以把這些標(biāo)準(zhǔn)看作filter的不同行為。這就像策略設(shè)計(jì)模式一樣,它讓你定義一組方法,把它們封裝起來,然后在運(yùn)行時選擇一個方法。這里,方法就是ApplePredicate,不同的策略就是AppleHeavyWeightPredicate和AppleGreenColorPredicate。 你可以將filterApples方法接受一個ApplePredicate對象,對Apple做條件測試。這樣就是行為參數(shù)化:讓方法接受多種行為作為參數(shù),并在內(nèi)部使用,來完成不同的行為。 根據(jù)抽象條件篩選 private static List<Apple> filterApples(List<Apple> apples, ApplePredicate<Apple> applePredicate) {
List<Apple> appleList = new ArrayList<>();
for (Apple apple : apples) {
if (applePredicate.test(apple)) {
appleList.add(apple);
}
}
return appleList;
}
代碼的傳遞/行為
酷,這段代碼看起來很多了,讀起來、用起來也更容易!現(xiàn)在你可以創(chuàng)建不同的ApplePredicate對象,并將它們傳遞給filterApples方法。這樣就可以根據(jù)不同的條件來創(chuàng)建一個類并且實(shí)現(xiàn)ApplePredicate就可以了。 現(xiàn)在,農(nóng)民要求需要篩選紅蘋果。那么,我們就可以根據(jù)條件創(chuàng)建一個類并且實(shí)現(xiàn)ApplePredicate: public class AppleRedAndHeavyPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return 'red'.equals(apple.getColor()) && apple.getWeight() > 150;
}
}
List<Apple> filterApples2 = filterApples(apples, new AppleRedAndHeavyPredicate());
System.out.println('通過謂詞篩選紅蘋果并且是重蘋果:' filterApples2);
酷,現(xiàn)在filterApples方法的行為已經(jīng)取決于通過ApplePredicate對象來實(shí)現(xiàn)了。這就是行為參數(shù)化了! 但是,你有沒有發(fā)現(xiàn),我們每次新增一個條件就需要新增一個類。這樣做有點(diǎn)太過于麻煩,或許我們可以通過Lambda,將表達(dá)式傳遞給filterApples方法,這樣就無需定義多個ApplePredicate類,從而去掉不必要的代碼,并減輕工作量。 多種行為,一個參數(shù)
行為參數(shù)化的好處在與你可以把迭代要篩選的集合的邏輯與對集合中每個元素應(yīng)用的行為區(qū)分開來。這樣你就可以重復(fù)使用同一個方法,給它不同的行為來達(dá)到不同的目的。 為了能夠?qū)?shù)化行為運(yùn)用自如,并且簡化代碼,我們來嘗試將參數(shù)通過Lambda的方式傳遞給filterApples。 通過Lambda的方式來篩選紅蘋果: List<Apple> filterApples3 = filterApples(apples, apple -> 'red'.equals(apple.getColor()));
通過Lambda的方式來篩選紅蘋果并且是重蘋果: List<Apple> filterApples4 = filterApples(apples, apple -> 'red'.equals(apple.getColor()) && apple.getWeight() > 150);
是的,使用的已經(jīng)還是原來的條件,并且不再需要根據(jù)不同的條件再去實(shí)現(xiàn)一個ApplePredicate類了,這樣極大的簡化了代碼。但是,農(nóng)民又有一個需求了:“現(xiàn)在,不只是需要對蘋果進(jìn)行篩選了,還需要對香蕉、橘子、草莓進(jìn)行篩選了?!?/p> 但是,我們目前的代碼只能夠?qū)μO果進(jìn)行篩選而已,為了解決這個問題,我們可以將類型定義為泛型,這樣就不只是只能對蘋果進(jìn)行篩選了。 使用謂詞其實(shí),我們可以不需要去定義謂詞,因?yàn)樵贘ava中就一個了Predicate了,我們可以使用它并且實(shí)現(xiàn)我們的功能。 定義一個泛型的filter方法: private static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T t : list) {
if (predicate.test(t)) {
result.add(t);
}
}
return result;
}
這個方法是一個通用的篩選方法,不只是可以用于篩選蘋果。 篩選重蘋果: List<Apple> heavyApples = filter(apples, (Apple apple) -> apple.getWeight() > 150);
篩選能被2整除的數(shù): List<Integer> numbers = Arrays.asList(10, 11, 8, 5, 1, 2, 29, 18);
List<Integer> integerList = filter(numbers, number -> number % 2 == 0);
是不是很酷?現(xiàn)在的代碼簡潔性和靈活性都很高,在Java8之前這些都是不可能做到的! 現(xiàn)在,你已經(jīng)感覺到了行為參數(shù)化是一個很有用的模式,它能夠輕松地適應(yīng)不斷變化的需求。在Java中很多方法都可以用不同的行為來參數(shù)化,比如使用Comparator排序,用Runnable執(zhí)行一個代碼塊等等。 使用Comparator來排序: apples.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
或者這樣: apples.sort(Comparator.comparing(Apple::getWeight));
使用Runnable執(zhí)行某個代碼塊: Thread t = new Thread(() -> System.out.println('HelloWorld'));
總結(jié)行為參數(shù)化,就是一個方法接受多個不同的行為作為參數(shù),并在內(nèi)部使用它們,完成不同行為的能力。 行為參數(shù)化可以讓代碼更好的適應(yīng)不斷變化的要求,減輕工作量。 傳遞代碼,就是將新行為作為參數(shù)傳遞給方法。但在Java8之前這實(shí)現(xiàn)起來很啰嗦。為接口生命許多只是用一次的實(shí)體類而造成的啰嗦代碼,在Java8之前采用匿名類來減少。 JavaAPI包含了很多可以用不同行為進(jìn)行參數(shù)化的方法,包括排序、線程等。
代碼示例:Github:chap2 公眾號
|