天天看點

java8實戰一:通過行為參數化傳遞代碼

通過行為參數化傳遞代碼

如何對你的代碼加以改進,進而更靈活地适應不斷變化的需求?

行為參數化就是可以幫你處理頻繁變更的需求的一種軟體開發模式.

一言以蔽之,它意味

着拿出一個代碼塊,把它準備好卻不去執行它。這個代碼塊以後可以被你程式的其他部分調用,

這意味着你可以推遲這塊代碼的執行。例如,你可以将代碼塊作為參數傳遞給另一個方法,稍後

再去執行它。這樣,這個方法的行為就基于那塊代碼被參數化了。

打個比方吧:

你的室友知道怎麼開車去超市,再開回家。于是你可

以告訴他去買一些東西,比如面包、奶酪、葡萄酒什麼的。這相當于調用一個 goAndBuy 方法,把

購物單作為參數。然而,有一天你在上班,你需要他去做一件他從來沒有做過的事情:從郵局取一

個包裹。現在你就需要傳遞給他一系列訓示了:去郵局,使用單号,和從業人員說明情況,取走包

裹。你可以把這些訓示用電子郵件發給他,當他收到之後就可以按照訓示行事了。你現在做的事情

就更進階一些了,相當于一個方法: go ,它可以接受不同的新行為作為參數,然後去執行.

應對不斷變化的需求

編寫能夠應對變化的需求的代碼并不容易。讓我們來看一個例子,我們會逐漸改進這個例子,

以展示一些讓代碼更靈活的最佳做法。就農場庫存程式而言,你必須實作一個從清單中篩選綠蘋

果的功能。聽起來很簡單吧?

第一個解決方案可能是下面這樣的:

/**
     *篩選綠蘋果
     * @param inventory 蘋果倉庫
     * @return
    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      

現在農民改主意了,他還想要篩選紅蘋果。

你該怎麼做呢?簡單的解決辦法就是複制這個方法,把名字改成 filterRedApples ,然後更改

if 條件來比對紅蘋果。然而,要是農民想要篩選多種顔色:淺綠色、暗紅色、黃色等,這種方法

就應付不了了。一個良好的原則是在編寫類似的代碼之後,嘗試将其抽象化。

/**
     * 把顔色參數化,應對顔色變化的需求
     * @param inventory
     * @param color
     * @return
    public static List<Apple> filterApplesByColor(List<Apple> inventory,
                                                  String color) {
        List<Apple> result = new ArrayList<Apple>();
        for (Apple apple: inventory){
            if ( apple.getColor().equals(color) ) {
                result.add(apple);
            }
        }
        return      

這時,農民不斷提新需求,按重量刷選,按裝量和顔色篩選,于是你不斷修改代碼,粘貼複制,

一種把所有屬性結合起來的笨拙嘗試如下所示:

public static List<Apple> filterApples(List<Apple> inventory, String color,
int weight, boolean flag) {
        List<Apple> result = new ArrayList<Apple>();
        for (Apple apple: inventory){
                if ( (flag && apple.getColor().equals(color)) ||
                    (!flag && apple.getWeight() > weight) ){
                    result.add(apple);
                }
            }
        return      

這個解決方案再差不過了。首先,用戶端代碼看上去糟透了。 true 和 false 是什麼意思?此

外,這個解決方案還是不能很好地應對變化的需求。如果這位農民要求你對蘋果的不同屬性做篩

選,比如大小、形狀、産地等,又怎麼辦?而且,如果農民要求你組合屬性,做更複雜的查詢,

比如綠色的重蘋果,又該怎麼辦?你會有好多個重複的 filter 方法,或一個巨大的非常複雜的

方法。

但如今這種情況下,你需要一種更好的方式,來把

蘋果的選擇标準告訴你的 filterApples 方法

行為參數化

你需要一種比添加很多參數更好的方法來應對變化的需求。讓

我們後退一步來看看更高層次的抽象。

一種可能的解決方案是對你的選擇标準模組化:你考慮的

是蘋果,需要根據 Apple 的某些屬性(比如它是綠色的嗎?重量超過150克嗎?)來傳回一個

boolean 值。我們把它稱為謂詞(即一個傳回 boolean 值的函數)。讓我們定義一個接口來對選

擇标準模組化:

public interface ApplePredicate

    boolean      

現在你就可以用 ApplePredicate 的多個實作代表不同的選擇标準了.

/**
 * 選出重蘋果的實作類
 *
 * @author itguang
 * @create
public class AppleHeavyWeightPredicate implements ApplePredicate{
    @Override
    public boolean test(Apple apple) {
        return apple.getWeight()>150;
    }
}      
/**
 * 選出綠蘋果的實作類
 *
 * @author itguang
 * @create
public class AppleGreenColorPredicate implements ApplePredicate
    @Override
    public boolean test(Apple apple) {
        return "green".equals(apple.getColor());
    }
}      

你可以把這些實作類看做filter方法的不同行為,剛做的這些和政策設計模式相關,他讓你定義一簇算法(稱為 :政策),然後在運作時選擇一個算法。在這裡,

算法族就是 ApplePredicate ,不同的政策就是 AppleHeavyWeightPredicate 和 AppleGreen-ColorPredicate 。

但是,該怎麼利用 ApplePredicate 的不同實作呢?

你需要 filterApples 方法接受

ApplePredicate 對象,對 Apple 做條件測試。這就是行為參數化:讓方法接受多種行為(或戰

略)作為參數,并在内部使用,來完成不同的行為。

filter 方法看起來是這樣的:

“`java 
 /** 
 * 根據抽象條件進行篩選 
 * @param inventory 
 * @param applePredicate 
 * @return 
 */ 
 public static List filter(List inventory, ApplePredicate applePredicate){ 
 ArrayList list = new ArrayList<>(); 
 for (Apple apple:list){ 
 if(applePredicate.test(apple)){ 
 list.add(apple); 
 } 
 } 
 return list;}      

“`

* 1. 傳遞代碼/行為:

這裡值得停下來小小地慶祝一下。這段代碼比我們第一次嘗試的時候靈活多了,讀起來、用

起來也更容易!現在你可以建立不同的 ApplePredicate 對象,并将它們傳遞給 filterApples

方法.

你已經做成了一件很酷的事: filterApples 方法的行為取決于你通過 ApplePredicate 對象傳遞的代碼。換句話說,你把 filterApples 方法的行為參數化了!

請注意,在上一個例子中,唯一重要的代碼是 test 方法的實作,如圖2-2所示;正是它定義

了 filterApples 方法的新行為。但令人遺憾的是,由于該 filterApples 方法隻能接受對象,

是以你必須把代碼包裹在 ApplePredicate 對象裡。你的做法就類似于在内聯“傳遞代碼”,因

為你是通過一個實作了 test 方法的對象來傳遞布爾表達式的

這就是說行為參數化是一個有用的概念的原因。你應該把它放進你的工具箱裡,用來編寫靈

活的API

對付啰嗦

到此,我們覺得已經做的很好了,我們使用行為參數化解決了一些棘手的問題.

但是,目前來說,當要把新的行為傳遞給

filterApples 方法的時候,你不得不聲明好幾個實作 ApplePredicate 接口的類,然後執行個體化

好幾個隻會提到一次的 ApplePredicate 對象。

Java有一個機制稱為匿名類,它可以讓你同時

聲明和執行個體化一個類。它可以幫助你進一步改善代碼,讓它變得更簡潔。但這也不完全令人滿意。它往往很笨重,因為它占用了很多空間。

使用 Lambda 表達式

“`java 
 //将 List 類型抽象化 
 public static List filter(List list, Predicate p){ 
 List result = new ArrayList<>(); 
 for(T e: list){ 
 if(p.test(e)){ 
 result.add(e); 
 } 
 } 
 return result; 
 }      
/**
     * 用Lambda表達式對倉庫的蘋果按重量升序排序
     */
    @Test
    public void test3(){
        List<Apple> inventory = initInventory();
        inventory.sort((Apple o1,Apple o2)->o1.getWeight().compareTo(o2.getWeight()));
        System.out.println(inventory);

    }      

現在暫時不用擔心這個新文法,下一章我們會詳細講解如何編寫和使用Lambda表達式。

舉例2:用 Runnable 執行代碼塊

線程就像是輕量級的程序:它們自己執行一個代碼塊。但是,怎麼才能告訴線程要執行哪塊

代碼呢?多個線程可能會運作不同的代碼。我們需要一種方式來代表稍候執行的一段代碼。在

Java裡,你可以使用 Runnable 接口表示一個要執行的代碼塊。請注意,代碼不會傳回任何結果

(即 void ):

// java.lang.Runnable
public interface Runnable{
     public void run();
}      

你可以像下面這樣,使用這個接口建立執行不同行為的線程:

Thread t = new Thread(new Runnable() {
   public void run(){
       System.out.println("Hello world");
   }
});      
Thread t = new Thread(() -> System.out.println("Hello world"));      

小結

  • 行為參數化,就是一個方法接受多個不同的行為作為參數,并在内部使用它們,完成不

    同行為的能力。

  • 行為參數化可讓代碼更好地适應不斷變化的要求,減輕未來的工作量。
  • 傳遞代碼,就是将新行為作為參數傳遞給方法。但在Java 8之前這實作起來很啰嗦。為接

    口聲明許多隻用一次的實體類而造成的啰嗦代碼,在Java 8之前可以用匿名類來減少。

  • Java API包含很多可以用不同行為進行參數化的方法,包括排序、線程和GUI處理。

小結

  • 行為參數化,就是一個方法接受多個不同的行為作為參數,并在内部使用它們,完成不

    同行為的能力。

  • 行為參數化可讓代碼更好地适應不斷變化的要求,減輕未來的工作量。
  • 傳遞代碼,就是将新行為作為參數傳遞給方法。但在Java 8之前這實作起來很啰嗦。為接

    口聲明許多隻用一次的實體類而造成的啰嗦代碼,在Java 8之前可以用匿名類來減少。

  • Java API包含很多可以用不同行為進行參數化的方法,包括排序、線程和GUI處理。