馬鞍山制作網(wǎng)站網(wǎng)絡(luò)營銷方式有哪幾種
項(xiàng)目源碼:https://github.com/java8/
1 應(yīng)對不斷變化的需求
在我們進(jìn)行開發(fā)中,經(jīng)常需要面臨需求的不斷變更,我們可以將行為參數(shù)化以適應(yīng)不斷變更的需求。
行為參數(shù)化就是可以幫助我們處理頻繁變更的需求的一種軟件開發(fā)模式
我們可以將代碼塊作為參數(shù)傳遞給方法。
例如,現(xiàn)有一個(gè)倉庫,我們想定義從倉庫中查詢綠蘋果的功能。后來我們又想查詢重蘋果(>150g)…
面對這種不斷變更的需求,我們就可以使用行為參數(shù)化來維護(hù)我們的代碼。
1.1 初試牛刀:篩選出綠蘋果
Apple類:
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;@Getter
@Setter
@AllArgsConstructor
@ToString
public class Apple {private int weight = 0;private String color = "";
}
// 初試牛刀:篩選綠蘋果
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;
}
? 如果說我們現(xiàn)在改變主意,想要查詢紅色的蘋果,最簡單的辦法就是copy上面這個(gè)方法,然后將方法改名為 fliterRedApples,改變if 判斷條件。談若我們需要查詢各種演示的蘋果:淺綠色、暗紅色、黃色等,那么再按照前面的這種方法來做代碼將變得非常的冗余。
? 一個(gè)好的原則是嘗試將我們上的的這個(gè)方法進(jìn)行抽象化,以適應(yīng)不同顏色的蘋果的查詢。
下面我們進(jìn)行嘗試:
1.2 再展身手:把顏色作為參數(shù)
我們立馬能想到的方法是將上面的 filterGreenApples 加上一個(gè)顏色參數(shù),就可以了:
// 再展身手:把顏色作為參數(shù)
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> greenApple = filterApplesByColor(inventory,"green");
List<Apple> redApple = filterApplesByColor(inventory,"red");
...
假設(shè)我們現(xiàn)在又要查詢重蘋果(>150g)或輕蘋果,那么我們只需要根據(jù)
filterApplesByColor 進(jìn)行稍稍修改即可,如下:
// 再展身手:根據(jù)蘋果的重量進(jìn)行查詢
public static List<Apple> filterApplesWeight(List<Apple> inventory, int weight) {List<Apple> result = new ArrayList<>();for (Apple apple : inventory) {if (apple.getWeight() > weight) {result.add(apple);}}return result;
}
現(xiàn)在我們完成了根據(jù)顏色查詢蘋果、根據(jù)重量查詢蘋果的功能,但是我們這兩個(gè)方法極為相似,復(fù)制了大部分的代碼來實(shí)現(xiàn)遍歷庫存,并對每個(gè)蘋果應(yīng)用篩選條件稍稍做了修改。
如果后續(xù)我們想改變查詢的遍歷方式來提升性能,那么需要修改所有的方法,這樣顯然是不合適的。
我們可以考慮將顏色和重量結(jié)合為一個(gè)方法,稱為filterApples。不過這樣需要加上一個(gè)標(biāo)記來區(qū)分是對什么屬性(顏色或重量)的查詢(但是我們不推薦這種方式)
1.3 第三次嘗試:對你能想到的每個(gè)屬性做篩選
下面是一個(gè)比較笨拙的方法:
// 生產(chǎn)環(huán)境別這么用
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)用這個(gè)方法,以使用不同屬性的查詢:
// 根據(jù)顏色查詢:查詢綠蘋果
List<Apple> greenApples = filterApples(inventory, "green",0,true);
// 根據(jù)重量查詢:查詢>150g的重蘋果
List<Apple> heavyApples = filterApples(inventory,"",150,false);
這個(gè)方法能解決根據(jù)不同屬性查詢蘋果的功能,但是這樣寫代碼很爛,并且我們也不知道 boolean flag 參數(shù)傳入 true、flase是什么意思,可讀性很差。
并且這樣也不能很好的使用根據(jù)不同屬性進(jìn)行查詢,如果我們需要根據(jù)多個(gè)屬性進(jìn)行查詢(比如:查詢綠色的重蘋果),那更是天方夜譚了。
下面我們來解決這個(gè)問題:
2 行為參數(shù)化
我們需要我們適應(yīng)各種各樣的屬性來查詢。
我們可以考慮根據(jù)Apple的屬性來返回一個(gè)boolean值。我們把它稱為謂詞(即一個(gè)返回boolean值的函數(shù))。下面我們定義一個(gè)接口來實(shí)現(xiàn):
// 判斷型接口
@FunctionalInterface
public interface ApplePredicate {boolean test(Apple apple);
}
現(xiàn)在我們就可以用 ApplePredicate 的多個(gè)實(shí)現(xiàn)代表不同的查詢標(biāo)準(zhǔn)了,例如:
// 查詢出重蘋果
public class AppleHeavyWeightPredicate implements ApplePredicate {@Overridepublic boolean test(Apple apple) {return apple.getWeight() > 150;}
}// 查詢出綠蘋果
public class AppleGreenColorPredicate implements ApplePredicate {@Overridepublic boolean test(Apple apple) {return "green".equals(apple.getColor());}
}
我們可以把這些標(biāo)準(zhǔn)看作filter方法的不同行為。剛做的這些和“策略設(shè)計(jì)模式”相關(guān),它讓你定義一族算法,把它們封裝起來(稱為“策略”),然后在運(yùn)行時(shí)選擇一個(gè)算法。在這里,算法族就是ApplePredicate,不同的策略就是AppleHeavyWeightPredicate和AppleGreenColorPredicate。
但是,該怎么利用ApplePredicate的不同實(shí)現(xiàn)呢?
我們需要 filterApples方法接受 ApplePredicate對象,對Apple做條件測試。這就是行為參數(shù)化:讓方法接受多種行為(或戰(zhàn)略)作為參數(shù),并在內(nèi)部使用,來完成不同的行為。
個(gè)人對于行為參數(shù)化的理解:
? 所謂行為參數(shù)化,就是將行為(一般封裝成方法的形式)以參數(shù)的形式傳遞到其他方法中執(zhí)行。
如何實(shí)現(xiàn)行為參數(shù)化:
我們要給filterApples方法添加一個(gè)參數(shù),讓它接受 ApplePredicate對象。
行為參數(shù)化的好處:
我們把filterApples方法迭代集合的邏輯與我們應(yīng)用到集合中每個(gè)元素的行為(這里是一個(gè)謂詞,即我們根據(jù)Apple的屬性查詢的行為)區(qū)分開了。
2.1 第四次嘗試:根據(jù)抽象條件篩選
利用 ApplePredicate 修改后的 filter方法如下:
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate predicate) {List<Apple> result = new ArrayList<>();for (Apple apple : inventory) {if (predicate.test(apple)){ // 謂詞對象封裝了測試蘋果的條件result.add(apple);}}return result;
}
2.2 傳遞代碼/行為
現(xiàn)在,這段代碼以及比我們前面寫的方法靈活多了,代碼的可讀性也高了。比如,我們要查詢紅的重蘋果,只需要?jiǎng)?chuàng)建一個(gè)類實(shí)現(xiàn)現(xiàn)ApplePredicate接口即可(甚至可以使用Lambda表達(dá)式):
public class AppleRedAndHeavyPredicate implements ApplePredicate{@Overridepublic boolean test(Apple apple) {return "red".equals(apple.getColor()) && apple.getWeight() > 150;}
}// 調(diào)用
List<Apple> redAndHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate());
現(xiàn)在,我們的filterApples方法完成了行為參數(shù)化。
但是我們現(xiàn)在發(fā)現(xiàn)這樣還是很麻煩,我們要完成一個(gè)查詢功能,為此我們定義了一個(gè)類,里面有一個(gè)方法,完成對查詢結(jié)果的判斷,這樣有很多的無用代碼,代碼可讀性還是不高。
我們可以使用lmabda表達(dá)式簡化不必要的代碼,直接把表達(dá)式"red".equals(apple.getColor())
&&apple.getWeight() > 150傳遞給filterApples方法,無需定義一個(gè)類
// 使用Lambda表達(dá)式實(shí)現(xiàn)傳遞代碼
List<Apple> redAndHeavyApples = filterApples(inventory, (apple) ->"red".equals(apple.getColor()) && apple.getWeight() > 150);
2.3 多種行為,一個(gè)參數(shù)
行為參數(shù)化的好處在于你可以把迭代要篩選的集合的邏輯與對集合中每個(gè)元素應(yīng)用的行為區(qū)分開來。這樣你可以重復(fù)使用同一個(gè)方法,給它不同的行為來達(dá)到不同的目的。
演示:編寫靈活的prettyPrintApple方法
編寫一個(gè)prettyPrintApple方法,它接受一個(gè)Apple的List,并可以對它參數(shù)化,以多種方式根據(jù)蘋果生成一個(gè)String輸出(有點(diǎn)兒像多個(gè)可定制的toString方法)。
例如,你可以告訴 prettyPrintApple 方法,只打印每個(gè)蘋果的重量。此外,你可以讓 prettyPrintApple方法分別打印每個(gè)蘋果,然后說明它是重的還是輕的。
@FunctionalInterface
public interface AppleFormatter{ String accept(Apple a);
}public class AppleFancyFormatter implements AppleFormatter { // 后續(xù)我們可以通過Lambda簡化,以省略這樣的類public String accept(Apple apple) {String characteristic = apple.getWeight() > 150 ? "heavy" :"light";return "A " + characteristic +" " + apple.getColor() + " apple";}
}public class AppleSimpleFormatter implements AppleFormatter {public String accept(Apple apple) {return "An apple of " + apple.getWeight() + "g";}
}public static void prettyPrintApple(List<Apple> inventory, AppleFormatter formatter) {for (Apple apple : inventory) {String output = formatter.accept(apple);System.out.println(output);}
}
使用
// 你首先要實(shí)例化AppleFormatter的實(shí)現(xiàn),然后把它們作為參數(shù)傳給prettyPrintApple方法
prettyPrintApple(inventory, new AppleFancyFormatter());
現(xiàn)在,我們已經(jīng)將行為抽象出來了,這樣使我們的代碼適應(yīng)不同的需求,但是這樣很繁瑣
因?yàn)槲覀冃枰暶骱芏鄠€(gè)只使用一次的類,下面通過匿名內(nèi)部類、Lambda表達(dá)式等方式進(jìn)行簡化
3 簡化代碼
在前面,當(dāng)要把新的行為傳遞給 filterApples方法的時(shí)候,我們不得不聲明好幾個(gè)實(shí)現(xiàn)ApplePredicate接口的類,然后實(shí)例化好幾個(gè)只會提到一次的ApplePredicate對象。這真是很啰嗦,很費(fèi)時(shí)間!
3.1 匿名類
匿名類和我們熟悉的 Java局部類(塊中定義的類)差不多,但匿名類沒有名字。它允許我們同時(shí)聲明并實(shí)例化一個(gè)類。換句話說,它允許你隨用隨建
3.2 第五次嘗試:使用匿名類
下面我們將通過創(chuàng)建匿名類的方式實(shí)現(xiàn)ApplePredicate的對象,重寫篩選的例子(以查詢綠色蘋果為例):
List<Apple> greenApples = filterApples(inventory, new ApplePredicate() { // 直接內(nèi)聯(lián)參數(shù)化filterapples方法的行為@Overridepublic boolean test(Apple apple) {return "red".equals(apple.getColor());}
});
但是匿名類的方式還是不夠好:
- 它往往很笨重,因?yàn)樗加昧撕芏嗫臻g
- 代碼閱讀性較差
總的來說,使用匿名類的方式,不僅代碼編寫、維護(hù)比較費(fèi)時(shí)間,可讀性也不太好。
接下來,我們使用Java 8中引人的Lambda表達(dá)式——一種更簡潔的傳遞代碼的方式。
3.3 第六次嘗試:使用 Lambda 表達(dá)式
使用Lambda表達(dá)式重寫上面的代碼:
List<Apple> greenApples = filterApples(inventory, (apple) ->"red".equals(apple.getColor()));
到目前為止,區(qū)別于以往的值參數(shù)傳遞,我們已經(jīng)實(shí)現(xiàn)了將類、匿名類、Lambda表達(dá)式等行為參數(shù)化傳遞到了方法中
3.4 第七次嘗試:將 List 類型抽象化
在通往抽象的路上,我們還可以更進(jìn)一步。目前,filterApples方法還只適用于Apple。
我們還可以將List類型抽象化,從而超越你眼前要處理的問題:
@FunctionalInterface
public interface Predicate<T>{ boolean test(T t);
}public static <T> List<T> filter(List<T> list, Predicate<T> p){ // 引入類型參數(shù)T,即泛型List<T> result = new ArrayList<>(); for(T e: list){ if(p.test(e)){ result.add(e); } } return result;
}
現(xiàn)在,我們的 filter 方法能更好的適應(yīng)不同的查詢了,可以用在香蕉、桔子、Integer或是String的列表等等上了。
例如:
List<Apple> redApples = filter(inventory, (Apple apple) -> "red".equals(apple.getColor())); // 從numbers中篩選出偶數(shù)
List<Integer> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0);
4 真實(shí)的例子
到現(xiàn)在,我們已經(jīng)清除的知道了行為參數(shù)化是一個(gè)很有用的模式,它能夠輕松地適應(yīng)不斷變化的需求。這種模式可以把一個(gè)行為(一段代碼)封裝起來,并通過傳遞和使用創(chuàng)建的行為(例如對Apple的不同謂詞)將方法的行為參數(shù)化。前面提到過,這種做法類似于策略設(shè)計(jì)模式。你可能已經(jīng)在實(shí)踐中用過這個(gè)模式了。Java API中的很多方法都可以用不同的行為來參數(shù)化。這些方法往往與匿名類一起使用。
我們會展示兩個(gè)例子,這應(yīng)該能幫助你鞏固傳遞代碼的思想了:用一個(gè)Comparator排序,用Runnable執(zhí)行一個(gè)代碼塊
4.1 用 Comparator 來排序
例如,根據(jù)蘋果的重量對庫存進(jìn)行排序,或者希望你根據(jù)顏色對蘋果進(jìn)行排序。聽起來有點(diǎn)兒耳熟?是的,
你需要一種方法來表示和使用不同的排序行為,來輕松地適應(yīng)變化的需求。
在Java 8中,List自帶了一個(gè)sort方法(你也可以使用Collections.sort)。sort的行為可以用java.util.Comparator
對象來參數(shù)化,如下:
package java.util;@FunctionalInterface
public interface Comparator<T> {int compare(T o1, T o2);...
}
因此,我們可以隨時(shí)創(chuàng)建Comparator的實(shí)現(xiàn),用sort方法表現(xiàn)出不同的行為。
比如,你可以 使用匿名類,按照蘋果的重量升序?qū)齑媾判?#xff1a;
inventory.sort(new Comparator<Apple>() {@Overridepublic int compare(Apple o1, Apple o2) {return o1.getWeight() - o2.getWeight();}
});
后續(xù),我們可以隨時(shí)創(chuàng)建一個(gè)Comparator來滿足新要求,并把它傳遞給 sort方法。而如何進(jìn)行排序這一內(nèi)部細(xì)節(jié)都被抽象掉了。用Lambda表達(dá)式的話,看起來就是這樣:
inventory.sort((Apple a1, Apple a2) -> a1.getWeight() - a2.getWeight());
4.2 用 Runnable 執(zhí)行代碼塊
在 Java里,你可以使用Runnable接口表示一個(gè)要執(zhí)行的代碼塊
package java.lang;@FunctionalInterface
public interface Runnable {public abstract void run();
}
我們可以像下面這樣,使用這個(gè)接口創(chuàng)建執(zhí)行不同行為的線程:
Thread t = new Thread(new Runnable() { @Overridepublic void run(){ System.out.println("Hello world"); }
});
使用Lambda表達(dá)式簡化:
Thread t = new Thread(() -> System.out.println("Hello world"));
5 小結(jié)
-
行為參數(shù)化,就是一個(gè)方法接受多個(gè)不同的行為作為參數(shù),并在內(nèi)部使用它們,完成不同行為的能力。
-
行為參數(shù)化可讓代碼更好地適應(yīng)不斷變化的要求,減輕未來的工作量。
-
傳遞代碼,就是將新行為作為參數(shù)傳遞給方法。但在Java 8之前這實(shí)現(xiàn)起來很啰嗦。為接口聲明許多只用一次的實(shí)體類而造成的啰嗦代碼,在Java 8之前可以用匿名類來減少。
-
Java API包含很多可以用不同行為進(jìn)行參數(shù)化的方法,包括排序、線程和GUI處理。