如何應(yīng)對不斷變化的需求
軟件工程中,用戶的需求肯定會變,如何應(yīng)對不斷變化的需求,理想狀態(tài)下,應(yīng)該把工作量降到最少,此外,類似的新功能實(shí)現(xiàn)還應(yīng)該很簡單,而且易于長期維護(hù)。
行為參數(shù)話就是可以幫助你處理頻繁變更需求的一種軟件開發(fā)模式:拿出一個(gè)代碼塊,準(zhǔn)備好不去執(zhí)行它,這個(gè)代碼塊以后可以被程序的其它部分調(diào)用,意味著可以推遲這塊代碼的執(zhí)行,例如,可以將代碼塊作為參數(shù)傳遞給另一個(gè)方法,稍后再去執(zhí)行它。
- 一個(gè)例子:農(nóng)場庫存一堆蘋果
1.篩選綠蘋果,第一個(gè)解決方案
public static List<Apple> filterGreenApples(List<Apple> appleList){
List<Apple> result = new ArrayList<Apple>();
for (Apple apple : appleList){
if("green".equals(apple.getColor())){
result.add(apple);
}
}
return result;
}
農(nóng)民改主意了,還想要篩選紅蘋果,該怎么做?簡單的解決辦法就是復(fù)制這個(gè)方法,把名字改成filterRedApples,更改if匹配條件,然而,農(nóng)民想要篩選多種顏色:淺綠色,暗紅色,黃色等,復(fù)制的方法應(yīng)付不了。
2.把顏色作為參數(shù), 靈活適應(yīng)變化
public static List<Apple> filterApplesByColor(List<Apple> appleList, String color){
List<Apple> result = new ArrayList<>();
for(Apple apple: appleList){
if(apple.getColor().equals(color)){
result.add(apple);
}
}
return result;
}
太簡單了!
農(nóng)民伯伯又跑來對你說,要是能區(qū)分,輕的蘋果和重的蘋果就太好了。
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;
}
不錯(cuò)的解決方案,但是,復(fù)制了大部分的代碼來實(shí)現(xiàn)遍歷庫存,對每個(gè)蘋果應(yīng)用篩選條件,打破了DRY的軟件開發(fā)原則,如果有要改變篩選的遍歷方式來提升性能呢?那就得修改所有方法的實(shí)現(xiàn).
3.第三次嘗試:對能想到的每個(gè)屬性做篩選
public static List<Apple> filterApple(List<Apple> appleList, String color, int weight, boolean flag){
List<Apple> result = new ArrayList<Apple>();
for(Apple apple: appleList){
/**十分笨拙的選擇顏色或重量的方法*/
if((flag && apple.getWeight() > weight) ||
(!flag && apple.getColor().equals(color))){
result.add(apple);
}
}
return result;
}
- 首先flag含義不清晰
- 還是不能很好的應(yīng)對變化的需求:大小,形狀,產(chǎn)地等,又怎么辦。
4.行為參數(shù)化
- 一種可能的解決方案是選擇標(biāo)準(zhǔn)建模:考慮的是根據(jù)蘋果的某些屬性來返回一個(gè)boolean值,稱只為謂詞(即一個(gè)返回boolean值的函數(shù))
定義一個(gè)接口對選擇標(biāo)準(zhǔn)建模:
interface ApplePredicate{
public boolean test(Apple a);
}
現(xiàn)在可以用ApplePredicate的多個(gè)實(shí)現(xiàn)代表不同的選擇標(biāo)準(zhǔn)
//選擇重的
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());
}
}

- 剛剛做的這些和“策略設(shè)計(jì)模式”相關(guān)
讓方法接受多種行為(或戰(zhàn)略)作為參數(shù),并在內(nèi)部使用,來完成不同的行為,這就是行為參數(shù)化。根據(jù)抽象條件篩選
public static List<Apple> filterApple(List<Apple> inventory, ApplePredicate p){
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory){
if(p.test(apple)){
result.add(apple);
}
}
return result;
}
- 靈活多了,易讀易用
filterApples方法的行為取決于通過ApplePredcate對象傳遞的代碼,換句話說,filterApples方法的行為參數(shù)化了。
行為參數(shù)化的好處在于,看可以把迭代要篩選的邏輯和對集合中每個(gè)元素應(yīng)用的行為區(qū)分開來,這樣可以重復(fù)使用一個(gè)方法,給它不同的行為來達(dá)到不同的目的。
但是上述行為的傳遞是通過行為封裝在對象中實(shí)現(xiàn)的,這樣不得不聲明好幾個(gè)實(shí)現(xiàn)謂詞接口的類,啰嗦且費(fèi)時(shí)。
5.匿名類
List<Apple> redApples2 = filterApple(inventory, new ApplePredicate() {
public boolean test(Apple a){
return a.getColor().equals("red");
}
});
- 笨重,占用代碼空間
- 經(jīng)典的Java謎題
6.使用Lambda表達(dá)式
List<Apple> redApples4 = filter(inventory,
(Apple apple) -> "red".equals(apple.getColor()));

7.將List類型抽象化
//引入泛型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;
}
現(xiàn)在可以把filter 用在香蕉,桔子,Integer,Sting 列表上了
行為參數(shù)化例子
//例子 排序
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
//線程執(zhí)行代碼塊
Thread t = new Thread(() -> System.out.println("Hello"));
//GUI事件處理
button.setOnAction((ActionEvent event) ->label.setText("Sent!"));
Lambda 表達(dá)式
-
lambda
簡潔的表示一個(gè)行為或傳遞代碼:沒有名稱,有參數(shù)列表,函數(shù)主體,返回類型,可能還有個(gè)可以拋出的異常列表
Lambda表達(dá)式.png
基本語法:
(parameters) ->expression
或
(parameters) -> {statements;} 哪里及如何使用
1.函數(shù)式接口
只定義一個(gè)抽象方法的接口,Predicate,Comparator, Runnable等
用函數(shù)式接口做什么呢,Lambda表達(dá)式允許直接以內(nèi)聯(lián)的形式為函數(shù)式接口的抽象方法提供實(shí)現(xiàn),并把整個(gè)表達(dá)式作為函數(shù)式接口的實(shí)例(具體實(shí)現(xiàn)的實(shí)例),用匿名類也可以完成同樣的事情,只是略笨拙。
2.函數(shù)描述符
函數(shù)式接口的抽象方法的簽名基本上就是Lambda表達(dá)式的簽名我們將這種抽象方法叫作
函數(shù)描述符,例如:
Runnable接口可以看作一個(gè)什么也不接受什么也不返回(void)的函數(shù)的簽名,因?yàn)樗挥幸粋€(gè)叫作run的抽象方法,這個(gè)方法什么也不接受,什么也不返回(void),() -> void代表了參數(shù)列表為空,且返回void的函數(shù)付諸實(shí)踐:環(huán)繞執(zhí)行模式
1.記得行為參數(shù)化
2.使用函數(shù)式接口傳遞行為
3.執(zhí)行一個(gè)行為
4.傳遞Lambda使用函數(shù)式接口
java.util.function包中引入了幾個(gè)新的函數(shù)式接口
1.Predicate
java.util.function.Predicate<T>接口定義了一個(gè)名叫test的抽象方法,它接受泛型T對象,并返回一個(gè)boolean。
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(stringList, nonEmptyStringPredicate);
2.Consumer
java.util.function.Consumer<T>定義了一個(gè)名叫accept的抽象方法,它接受泛型T的對象,沒有返回(void),你如果需要訪問類型T的對象,并對其執(zhí)行某些操作,就可以使用這個(gè)接口
forEach(Arrays.asList(1,3,4,5), (Integer i) -> System.out.println(i));
3.Function
java.util.function.Function<T, R>接口定義了一個(gè)叫作apply的方法,它接受一個(gè)泛型T的對象,并返回一個(gè)泛型R的對象。如果你需要定義一個(gè)Lambda,將輸入對象的信息映射到輸出,就可以使用這個(gè)接口(比如提取蘋果的重量,或把字符串映射為它的長度)
List<Integer> l = map(Arrays.asList("lambda", "Eric", "stream"), (String s) -> s.length());
4.原始類型特性化
Java類型要么是引用類型(比如Byte、 Integer、 Object、 List) ,要么是原始類型(比如int、 double、 byte、 char)。但是泛型(比如Consumer<T>中的T)只能綁定到引用類型。
IntPredicate 等等等 避免使用Lambda時(shí)自動裝箱,拆箱操作
-
函數(shù)式接口 類型檢查
Lambda的類型是從使用Lambda的上下文推斷出來的
1-Lambda-類型檢查.png
同一個(gè)Lambda可用于多個(gè)不同的函數(shù)式接口
- 類型推斷
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
使用局部變量
局部變量必須顯式聲明為final,或事實(shí)上是final。
1.第一,實(shí)例變量和局部變量背后的實(shí)現(xiàn)有一個(gè)關(guān)鍵不同。實(shí)例變量都存儲在堆中,而局部變量則保存在棧上。如果Lambda可以直接訪問局部變量,而且Lambda是在一個(gè)線程中使用的,則使用Lambda的線程,可能會在分配該變量的線程將這個(gè)變量收回之后,去訪問該變量。因此, Java在訪問自由局部變量時(shí),實(shí)際上是在訪問它的副本,而不是訪問原始變量。
2.這一限制不鼓勵(lì)你使用改變外部變量的典型命令式編程模式,這種模式會阻礙很容易做到的并行處理。-
方法引用
方法引用可以被看作僅僅調(diào)用特定方法的Lambda的一種快捷寫法,方法引用就是讓你根據(jù)已有的方法實(shí)現(xiàn)來創(chuàng)建Lambda表達(dá)式
等效方法引用.png
如何構(gòu)建方法引用

構(gòu)造函數(shù)引用
不將構(gòu)造函數(shù)實(shí)例化卻能夠引用它,這個(gè)功能有一些有趣的應(yīng)用
static Map<String, Function<Integer, Fruit>> map = new HashMap<>();
static{
map.put("apple", Apple::new);
map.put("orange", Orange::new);
}
public static Fruit getMeFruit(String fruit, Integer weight){
return map.get(fruit).apply(weight);
}
- Lambda復(fù)合
Java 8的好幾個(gè)函數(shù)式接口都有為方便而設(shè)計(jì)的方法。具體而言,許多函數(shù)式接口,比如用于傳遞Lambda表達(dá)式的Comparator、Function和Predicate都提供了允許你進(jìn)行復(fù)合的方法,這意味著你可以把多個(gè)簡單的Lambda復(fù)合成復(fù)雜的表達(dá)式。比如,可以讓兩個(gè)謂詞之間做一個(gè)or操作,組合成一個(gè)更大的謂詞。而且,你還可以讓一個(gè)函數(shù)的結(jié)果成為另一個(gè)函數(shù)的輸入。
1.比較器復(fù)合
Comparator<Apple> c = comparing(Apple::getWeight);
/**逆序*/
inventory.sort(comparing(Apple::getWeight).reversed());
/**比較器鏈*/
inventory.sort(comparing(Apple::getWeight).reversed().thenComparing(Apple::getColor));
2.謂詞復(fù)合
Predicate<Apple> redApple = a -> "red".equals(a.getColor());
Predicate<Apple> notRedApple = redApple.negate();
Predicate<Apple> heavyApple = a -> a.getWeight() > 100;
Predicate<Apple> redAndHeavyApple = redApple.and(heavyApple);
3.函數(shù)復(fù)合
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
//g(f(x))
Function<Integer, Integer> h = f.andThen(g);
int result = h.apply(1);
//f(g(x))
Function<Integer, Integer> h1 = f.compose(g);
int result2 = h1.apply(1);
- 數(shù)學(xué)中的類似思想-積分


