通過行為參數傳遞代碼

應對不斷變化的需求

目標

在軟件工程中一個眾所周知的問題就是,不管你做什么,用戶的需求肯定會變。比如一位農民第一天可能有一個想要查找庫存中所有綠色蘋果的功能,但第二天可能又想要找出重量大于 150 克的水果,可能過幾天又會變成既要綠色蘋果重量又要大于 150 克。我們該如何應對不斷變化的需求?我們最理想的狀態為:工作量降到最少,新添加的功能實現起來非常簡單而且易于長期維護

行為參數化是什么

行為參數化就是可以幫助你處理頻繁變更的需求的一種軟件開發模式。一言以蔽之,它意味著拿出一個代碼塊,把他準備好卻不去執行它。這個代碼塊以后可以被你程序的其他部分調用,這意味著你可以推遲這塊代碼的執行。例如,你可以將代碼塊作為參數傳遞給另一個方法,稍后再去執行它。這樣,這個方法的行為就基于那塊代碼被參數化了。

下面我們結合上面提到的農民的例子來應對不斷變化的需求。

篩選綠蘋果

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;
}

篩選出綠色蘋果很簡單吧,但是農民如果要篩選紅色蘋果呢?最簡單的解決方法就是復制這個方法,把名字改成 filterRedApples,然后改一下 if 條件來匹配紅蘋果。然而如果顏色更多呢?我們不可能寫一大溜的方法來匹配不同的顏色吧!一個良好的原則是在編寫類似的代碼后嘗試將其抽象化

把顏色作為參數

一種做法是給方法加一個參數,把顏色變成參數,這樣就能靈活地適應變化了:

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> greenApples=filterApplesByColor(inventory,"green");
    List<Apple> redApples=filterApplesByColor(inventory,"red");

太簡單了吧?那如果農民想要區分輕的蘋果和重的蘋果呢?

對你能想到的每個屬性做篩選

我們寫一個顏色和重量結合的方法,再加上一個 flag 來區分想要篩選哪個屬性。

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;
}

調用:

    List<Apple> greenApples = filterApples(inventory, "green",150,true);
    List<Apple> redApples = filterApples(inventory, "red",120,false);

上面的方法好像依然很輕松的解決了農民朋友的問題,但是客戶端代碼看起來糟透了,true 和 false 又是什么意思?此外解決方案還是不能很好的應對變化的需求。如果這位農民要求你對蘋果的不同屬性做篩選,比如大小、形狀、產地等,又怎么辦?而且農民要求你組合屬性,做更復雜的查詢,又該怎么辦?你會有好多重復的 filter 方法,或一個巨大的非常復雜的方法。我們下一節就開始利用行為參數化來實現更加靈活的方法。

行為參數化

首先我們后退一步來進行更高層次的抽象:我們考慮的是蘋果,需要根據 Apple 的一些屬性(比如它是綠色的嗎?重量超過 150 克嗎?)來返回一個 boolean 值。我們把它稱為謂詞(即一個返回 boolean 值的函數)。讓我們定義一個接口來對選擇標準建模:

public interface ApplePredicate {
    boolean test (Apple apple);
}

現在我們可以使用 ApplePredicate 的多個實現代表不同的選擇標準了:

篩選重量大于 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());
    }
}

我們這樣做已經非常類似“策略設計模式”了,在這里算法族就是 ApplePredicate,不同的策略就是 AppleHeavyWeightPredicate 或者 AppleGreenColorPredicate。

在我們的例子中根據抽象條件篩選:

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;
}

現在我們的代碼已經靈活多了,讀起來用起來都更容易!如果你要篩選紅色并且重量大于 200 克的蘋果只需要再寫一個策略:

public class AppleRedAndHeavyPredicate implements ApplePredicate {
    @Override
    public boolean test(Apple apple) {
        return "red".equals(apple.getColor())&&apple.getWeight()>200;
    }
}

使用匿名類加上 Lambda 表達式:

使用匿名內部類:

我們上面所做的,依然有很多不足,我們需要聲明很多只要實例化一次的類,費那么大的勁真的沒必要,我們可以做的更好嗎?Java 中有匿名內部類,我們利用它進一步改善代碼:

    List<Apple> greenApples = filterApples(inventory, new ApplePredicate() {
        @Override
        public boolean test(Apple apple) {
            return "green".equals(apple.getColor());
        }
    });

使用 Lambda表達式

匿名內部類還是不夠好,它比較笨重,占用了很多的空間。而且內部類一般比較難讀。在 Java8 中新添加了 Lambda 表達式,我們如果利用了它只需要一句話就解決了問題:

    List<Apple> greenApples = filterApples(inventory, (Apple apple)->"green".equals(apple.getColor()));

將 List 類型抽象化

通往抽象的路上,我們還可以更進一步。目前我們還只適用于 Apple,我們還可以將 List 類型抽象化,從而超越眼前所要處理的問題:

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;
}

現在我們可以把filter方法用在香蕉、桔子、 Integer 或者是 String 上了,比如說我們要找出一個數字集合中的所有偶數,同樣只需要一句話:

    List<Integer> evenNumbers=filter(numbers,(i)->i%2==0);

Lambda 表達式鞏固練習

用 Comparator 升序排序

    //對蘋果重量進行排序
    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 表達式
    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 的簡單使用

提前準備:

defaultConfig {
    ...
    jackOptions {
        enabled true
    }
}

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

點擊事件調用:

    findViewById(R.id.btn).setOnClickListener((v) -> Toast.makeText(this, "hello", Toast.LENGTH_SHORT).show());

參考

本文純屬《Java8 實戰》學習筆記

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,048評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,414評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,169評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,722評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,465評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,823評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,813評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,000評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,554評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,295評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,513評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,722評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,125評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,430評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,237評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,482評論 2 379

推薦閱讀更多精彩內容