應(yīng)對(duì)不斷變化的需求
目標(biāo)
在軟件工程中一個(gè)眾所周知的問(wèn)題就是,不管你做什么,用戶的需求肯定會(huì)變。比如一位農(nóng)民第一天可能有一個(gè)想要查找?guī)齑嬷兴芯G色蘋果的功能,但第二天可能又想要找出重量大于 150 克的水果,可能過(guò)幾天又會(huì)變成既要綠色蘋果重量又要大于 150 克。我們?cè)撊绾螒?yīng)對(duì)不斷變化的需求?我們最理想的狀態(tài)為:工作量降到最少,新添加的功能實(shí)現(xiàn)起來(lái)非常簡(jiǎn)單而且易于長(zhǎng)期維護(hù)。
行為參數(shù)化是什么
行為參數(shù)化就是可以幫助你處理頻繁變更的需求的一種軟件開(kāi)發(fā)模式。一言以蔽之,它意味著拿出一個(gè)代碼塊,把他準(zhǔn)備好卻不去執(zhí)行它。這個(gè)代碼塊以后可以被你程序的其他部分調(diào)用,這意味著你可以推遲這塊代碼的執(zhí)行。例如,你可以將代碼塊作為參數(shù)傳遞給另一個(gè)方法,稍后再去執(zhí)行它。這樣,這個(gè)方法的行為就基于那塊代碼被參數(shù)化了。
下面我們結(jié)合上面提到的農(nóng)民的例子來(lái)應(yīng)對(duì)不斷變化的需求。
篩選綠蘋果
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;
}
篩選出綠色蘋果很簡(jiǎn)單吧,但是農(nóng)民如果要篩選紅色蘋果呢?最簡(jiǎn)單的解決方法就是復(fù)制這個(gè)方法,把名字改成 filterRedApples,然后改一下 if 條件來(lái)匹配紅蘋果。然而如果顏色更多呢?我們不可能寫(xiě)一大溜的方法來(lái)匹配不同的顏色吧!一個(gè)良好的原則是在編寫(xiě)類似的代碼后嘗試將其抽象化。
把顏色作為參數(shù)
一種做法是給方法加一個(gè)參數(shù),把顏色變成參數(shù),這樣就能靈活地適應(yīng)變化了:
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;
}
只要像下面這樣調(diào)用就可以了:
List<Apple> greenApples=filterApplesByColor(inventory,"green");
List<Apple> redApples=filterApplesByColor(inventory,"red");
太簡(jiǎn)單了吧?那如果農(nóng)民想要區(qū)分輕的蘋果和重的蘋果呢?
對(duì)你能想到的每個(gè)屬性做篩選
我們寫(xiě)一個(gè)顏色和重量結(jié)合的方法,再加上一個(gè) flag 來(lái)區(qū)分想要篩選哪個(gè)屬性。
public static List<Apple> filterApples(List<Apple> inventory, String color, int weight, boolean flag) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if ((flag && apple.getColor().equals(color)) || (flag && apple.getWeight() > weight)) {
result.add(apple);
}
}
return result;
}
調(diào)用:
List<Apple> greenApples = filterApples(inventory, "green",150,true);
List<Apple> redApples = filterApples(inventory, "red",120,false);
上面的方法好像依然很輕松的解決了農(nóng)民朋友的問(wèn)題,但是客戶端代碼看起來(lái)糟透了,true 和 false 又是什么意思?此外解決方案還是不能很好的應(yīng)對(duì)變化的需求。如果這位農(nóng)民要求你對(duì)蘋果的不同屬性做篩選,比如大小、形狀、產(chǎn)地等,又怎么辦?而且農(nóng)民要求你組合屬性,做更復(fù)雜的查詢,又該怎么辦?你會(huì)有好多重復(fù)的 filter 方法,或一個(gè)巨大的非常復(fù)雜的方法。我們下一節(jié)就開(kāi)始利用行為參數(shù)化來(lái)實(shí)現(xiàn)更加靈活的方法。
行為參數(shù)化
首先我們后退一步來(lái)進(jìn)行更高層次的抽象:我們考慮的是蘋果,需要根據(jù) Apple 的一些屬性(比如它是綠色的嗎?重量超過(guò) 150 克嗎?)來(lái)返回一個(gè) boolean 值。我們把它稱為謂詞(即一個(gè)返回 boolean 值的函數(shù))。讓我們定義一個(gè)接口來(lái)對(duì)選擇標(biāo)準(zhǔn)建模:
public interface ApplePredicate {
boolean test (Apple apple);
}
現(xiàn)在我們可以使用 ApplePredicate 的多個(gè)實(shí)現(xiàn)代表不同的選擇標(biāo)準(zhǔn)了:
篩選重量大于 150 的蘋果:
public class AppleHeavyWeightPredicate implements ApplePredicate{
@Override
public boolean test(Apple apple) {
return apple.getWeight()>150;
}
}
篩選綠色蘋果:
public class AppleGreenColorPredicate implements ApplePredicate{
@Override
public boolean test(Apple apple) {
return "green".equals(apple.getColor());
}
}
我們這樣做已經(jīng)非常類似“策略設(shè)計(jì)模式”了,在這里算法族就是 ApplePredicate,不同的策略就是 AppleHeavyWeightPredicate 或者 AppleGreenColorPredicate。
在我們的例子中根據(jù)抽象條件篩選:
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;
}
現(xiàn)在我們的代碼已經(jīng)靈活多了,讀起來(lái)用起來(lái)都更容易!如果你要篩選紅色并且重量大于 200 克的蘋果只需要再寫(xiě)一個(gè)策略:
public class AppleRedAndHeavyPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return "red".equals(apple.getColor())&&apple.getWeight()>200;
}
}
使用匿名類加上 Lambda 表達(dá)式:
使用匿名內(nèi)部類:
我們上面所做的,依然有很多不足,我們需要聲明很多只要實(shí)例化一次的類,費(fèi)那么大的勁真的沒(méi)必要,我們可以做的更好嗎?Java 中有匿名內(nèi)部類,我們利用它進(jìn)一步改善代碼:
List<Apple> greenApples = filterApples(inventory, new ApplePredicate() {
@Override
public boolean test(Apple apple) {
return "green".equals(apple.getColor());
}
});
使用 Lambda表達(dá)式
匿名內(nèi)部類還是不夠好,它比較笨重,占用了很多的空間。而且內(nèi)部類一般比較難讀。在 Java8 中新添加了 Lambda 表達(dá)式,我們?nèi)绻昧怂恍枰痪湓捑徒鉀Q了問(wèn)題:
List<Apple> greenApples = filterApples(inventory, (Apple apple)->"green".equals(apple.getColor()));
將 List 類型抽象化
通往抽象的路上,我們還可以更進(jìn)一步。目前我們還只適用于 Apple,我們還可以將 List 類型抽象化,從而超越眼前所要處理的問(wèn)題:
public interface Predicate<T>{
boolean test(T t);
}
修改filter方法:
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;
}
現(xiàn)在我們可以把filter方法用在香蕉、桔子、 Integer 或者是 String 上了,比如說(shuō)我們要找出一個(gè)數(shù)字集合中的所有偶數(shù),同樣只需要一句話:
List<Integer> evenNumbers=filter(numbers,(i)->i%2==0);
Lambda 表達(dá)式鞏固練習(xí)
用 Comparator 升序排序
//對(duì)蘋果重量進(jìn)行排序
List<Apple> inventory = new ArrayList<>();
inventory.add(new Apple("green", 155));
inventory.add(new Apple("green", 177));
inventory.add(new Apple("green", 244));
inventory.add(new Apple("red", 123));
//原始方法
//Collections.sort(inventory, new Comparator<Apple>() {
// @Override
// public int compare(Apple a1, Apple a2) {
// return a1.getWeight() > a2.getWeight() ? -1 : a1.getWeight() < a2.getWeight() ? 1 : 0;
// }
//});
//使用 Lambda 表達(dá)式
Collections.sort(inventory, (a1, a2) -> a1.getWeight() > a2.getWeight() ? 1 : a1.getWeight() < a2.getWeight() ? -1 : 0);
for (Apple apple : inventory) {
System.out.println(apple.getWeight());
}
Thread 的使用
new Thread(()->System.out.println("Hello world")).start();
Android 中 Lambda 的簡(jiǎn)單使用
提前準(zhǔn)備:
defaultConfig {
...
jackOptions {
enabled true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
點(diǎn)擊事件調(diào)用:
findViewById(R.id.btn).setOnClickListener((v) -> Toast.makeText(this, "hello", Toast.LENGTH_SHORT).show());
參考
本文純屬《Java8 實(shí)戰(zhàn)》學(xué)習(xí)筆記