行為參數(shù)化是可以幫助你處理頻繁變更的需求的一種軟件開(kāi)發(fā)模式
引言
1.首先我們看下實(shí)現(xiàn)從蘋(píng)果列表中選出所有的綠色的蘋(píng)果的代碼
public static List<Apple> filterGreenApples(List<Apple> inventory){
//存儲(chǔ)所有選出的蘋(píng)果
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory){
//選出綠色蘋(píng)果
if("green".equals(apple.getColor())){
result.add(apple);
}
}
return result;
}
結(jié)果輸出
public static void main(String[] args) {
// 蘋(píng)果列表
List<Apple> list = Arrays.asList(new Apple(80,"green"),new Apple(155,"green"),new Apple(120,"red"));
//挑選蘋(píng)果
List<Apple> listGreen = filterGreenApples(list);
for(Apple apple:listGreen){
System.out.println("蘋(píng)果顏色是:"+apple.getColor()+"褐捻;重量是"+apple.getWeight()+"g狸驳。");
}
}
//執(zhí)行結(jié)果
蘋(píng)果顏色是:green;重量是80g状知。
蘋(píng)果顏色是:green;重量是155g孽查。
2.當(dāng)我們只有這一個(gè)需求的時(shí)候饥悴,上述實(shí)現(xiàn)沒(méi)有問(wèn)題,如果我們需要篩選紅色的蘋(píng)果呢,那我們可以將顏色作為參數(shù)進(jìn)行實(shí)現(xiàn)
public static List<Apple> filterGreenApples(List<Apple> inventory,String color){
//存儲(chǔ)所有選出的蘋(píng)果
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory){
//選出顏色為color的蘋(píng)果
if(apple.getColor().equals(color)){
result.add(apple);
}
}
return result;
}
此時(shí)我們可以通過(guò)顏色挑選蘋(píng)果
//挑選綠色蘋(píng)果
List<Apple> listGreen = filterGreenApples(list,"green");
//挑選紅色蘋(píng)果
List<Apple> listRed = filterGreenApples(list,"red");
//挑選xx顏色的蘋(píng)果
List<Apple> listRed = filterGreenApples(list,"xx");
……
3.這時(shí)候我們又有新的需求來(lái)了西设,我們要根據(jù)蘋(píng)果的重量來(lái)篩選瓣铣,比如篩選重量大于150的蘋(píng)果,我們根據(jù)上面的解決辦法贷揽,實(shí)現(xiàn)如下:
public static List<Apple> filterGreenApples(List<Apple> inventory, int weight){
//存儲(chǔ)所有選出的蘋(píng)果
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory){
//選出重量大于weight的蘋(píng)果
if(apple.getWeight()>weight){
result.add(apple);
}
}
return result;
}
此時(shí)我們可以通過(guò)蘋(píng)果的重量挑選蘋(píng)果
//挑選蘋(píng)果
List<Apple> listWeight = filterGreenApples(list,150);
//執(zhí)行結(jié)果
4.那么當(dāng)我們想要既能通過(guò)蘋(píng)果的重量篩選&也能通過(guò)顏色進(jìn)行篩選呢棠笑?我們需要添加上述2和3的兩個(gè)代碼;當(dāng)然擒滑,我們還是可以根據(jù)上面的思路腐晾,然后增加一個(gè)標(biāo)志位來(lái)判斷是根據(jù)顏色還是根據(jù)重量篩選,實(shí)現(xiàn)如下:
public static List<Apple> filterGreenApples(List<Apple> inventory, String color,int weight,boolean flag){
//存儲(chǔ)所有選出的蘋(píng)果
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory){
// 參數(shù)flag表示標(biāo)志位丐一,flag為true表示通過(guò)顏色篩選藻糖,false表示通過(guò)重量篩選
if((flag && apple.getColor().equals(color))||(!flag && apple.getWeight()>weight)){
result.add(apple);
}
}
return result;
}
調(diào)用方式如下:
// 通過(guò)顏色篩選
List<Apple> listColor = filterGreenApples(list,"green",150,true);
// 通過(guò)重量篩
List<Apple> listWeight = filterGreenApples(list,"green",150,false);
建議最好不要這樣用,首先可讀性不強(qiáng)库车,flag的true和false分別代表什么呢巨柒?其次,當(dāng)Apple含有其他屬性時(shí)(比如產(chǎn)地柠衍、甜度等)洋满,編寫(xiě)的代碼就會(huì)比較復(fù)雜,這時(shí)候就需要引入“行為參數(shù)化”這個(gè)開(kāi)發(fā)模式珍坊,行為參數(shù)化就是用來(lái)處理頻繁變更的需求的開(kāi)發(fā)模式牺勾。
行為參數(shù)化傳遞參數(shù)的方式
1.建模,即選擇的蘋(píng)果的策略
引言中我們使用的方式是“值的參數(shù)化”阵漏,這種方式并不靈活驻民,我們需要將蘋(píng)果的屬性抽象出來(lái)(不考慮實(shí)際有哪些屬性),返回一個(gè)boolean值履怯,告訴用戶(hù)是否滿(mǎn)足篩選條件(即符合這個(gè)屬性)回还;這就是一個(gè)抽象的標(biāo)準(zhǔn),我們通過(guò)這個(gè)標(biāo)準(zhǔn)建模:
public interface ApplePredicate {
// 任何類(lèi)要實(shí)現(xiàn)此接口都要實(shí)現(xiàn)test方法叹洲,傳入的參數(shù)必須是Apple類(lèi)型柠硕,返回值為boolean
boolean test (Apple apple);
}
2.使用多個(gè)實(shí)現(xiàn)實(shí)現(xiàn)不同的選擇標(biāo)準(zhǔn)
這時(shí)候我們可以通過(guò)接口的實(shí)現(xiàn)類(lèi)實(shí)現(xiàn)多個(gè)篩選標(biāo)準(zhǔn)了,比如我們需要篩選出重的蘋(píng)果运提,那我們就可以這樣實(shí)現(xiàn):
public class AppleHeavyWeightPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return apple.getWeight()>150;
}
}
篩選出綠色的蘋(píng)果的實(shí)現(xiàn)如下:
public class AppleGreenColorPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return "green".equals(apple.getColor());
}
}
3.傳遞代碼
這時(shí)候我們將ApplePredicate進(jìn)行了各種實(shí)現(xiàn)蝗柔,我們?cè)趺蠢眠@些實(shí)現(xiàn)來(lái)達(dá)到我們篩選的目的呢?我們需要給filterApples方法添加一個(gè)參數(shù)民泵,讓它接收ApplePredicate對(duì)象诫咱,對(duì)Apple對(duì)象進(jìn)行條件測(cè)試。這就是行為參數(shù)化:讓方法接受多種行為(或戰(zhàn)略)作為參數(shù)洪灯,并在內(nèi)部使用坎缭,來(lái)完成不同的行為竟痰。那我們看下修改后的filterApples方法:
public static List<Apple> filterApples(List<Apple> inventory,ApplePredicate p){
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory){
//ApplePredicate對(duì)象p封裝了測(cè)試蘋(píng)果的條件
if(p.test(apple)){
result.add(apple);
}
}
return result;
}
- 傳遞代碼/行為
//首先AppleHeavyWeightPredicate和AppleGreenColorPredicate都是ApplePredicate的實(shí)現(xiàn)類(lèi)
ApplePredicate p1 = new AppleHeavyWeightPredicate();
ApplePredicate p2 = new AppleGreenColorPredicate();
看到這邊相信大家都知道下一步怎么用這個(gè)方式來(lái)篩選一個(gè)綠色的蘋(píng)果了
List<Apple> listGreen = filterApples(list,new AppleGreenColorPredicate());
這表明filterApples方法的行為取決于你通過(guò)ApplePredicate對(duì)象傳遞的代碼,這就相當(dāng)于你把filterApple方法行為參數(shù)化了掏呼。
其實(shí)在這個(gè)例子當(dāng)中坏快,最重要的就是test方法的實(shí)現(xiàn),它定義了filterApples方法的新行為憎夷,但是filterApples只能接受對(duì)象莽鸿,所以我們必須把test方法實(shí)現(xiàn)的代碼包裹在ApplePredicate對(duì)象里,即我們通過(guò)一個(gè)實(shí)現(xiàn)了test方法的對(duì)象來(lái)傳遞布爾表達(dá)式拾给,這種做法類(lèi)似于用在“傳遞代碼”祥得。通過(guò)下面的圖我們形象的看一下
-
多種行為,一個(gè)參數(shù)
行為參數(shù)化的好處在于你可以把迭代要篩選的集合的邏輯與對(duì)集合中么個(gè)元素應(yīng)用的行為區(qū)分開(kāi)來(lái)蒋得,這樣我們可以重復(fù)使用同一個(gè)方法级及,給它不同的行為來(lái)達(dá)到不同的目的。
4.優(yōu)化代碼
通過(guò)上面這種實(shí)現(xiàn)方式额衙,雖然可以讓代碼適應(yīng)需求的變化饮焦,但是這個(gè)過(guò)程很啰嗦,我們需要聲明很多只要實(shí)例化一次的類(lèi)窍侧。這時(shí)候我們就想到了java中的匿名類(lèi)县踢。那我們的實(shí)現(xiàn)如下:
定義篩選標(biāo)準(zhǔn)建模&filterApples實(shí)現(xiàn)方式不變,只是將需要實(shí)例化的類(lèi)使用匿名類(lèi)聲明
//篩選紅色蘋(píng)果
List<Apple> listRed= filterApples(list, new ApplePredicate() {
@Override
public boolean test(Apple apple) {
return "red".equals(apple.getColor());
}
});
這種方式雖然省去了聲明類(lèi)的這個(gè)步驟伟件,但是還是也還是存在不易讀以及笨重的問(wèn)題硼啤,java8中通過(guò)lambda表達(dá)式很好的解決了這個(gè)問(wèn)題,讓代碼看起來(lái)更干凈斧账。
上述代碼可以使用lambda重寫(xiě)如下:
//篩選紅色蘋(píng)果
List<Apple> listRed2= filterApples(list,(Apple apple)->"red".equals(apple.getColor()));
5.list類(lèi)型的抽象化
進(jìn)行過(guò)上述抽象后谴返,filterApples方法只適用于Apple,我們還可以通過(guò)將list類(lèi)型抽象化其骄,使我們的代碼更加靈活亏镰。
- 抽象標(biāo)準(zhǔ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;
}
- 調(diào)用方式一致
//篩選紅色蘋(píng)果
List<Apple> listRed= filterApples(list,(Apple apple)->"red".equals(apple.getColor()));
這時(shí)候filter方法就不僅僅可以用于Apple的列表了扯旷,香蕉拯爽、橘子等都可以適用了。