Java8 in action(1) 通過行為參數化傳遞代碼--lambda代替策略模式


豬腳:以下內容參考《Java 8 in Action》

發布:https://ryan-miao.github.io/2017/07/15/java8-in-action-2/

源碼:github

需求

果農需要篩選蘋果,可能想要綠色的,也可能想要紅色的,可能想要大蘋果(>150g),也可能需要紅的大蘋果。基於此等條件,編寫篩選的代碼。

1. 策略模式解決方案

1.1 最直觀的做法

首先,已知信息是一筐蘋果(List<Apple> inventory),但篩選條件多種多樣。我們可以根據不同的條件寫不同的方法來達到目的。比如,找出綠色的蘋果:

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

同樣的,可以編寫filterRed, filterWeight等等。但必然出現重復代碼,違反軟件工程原則Don't repeast yourself。而且,篩選的類也會顯得臃腫。

現在,有一種更容易維護,更容易閱讀的策略模式來實現這個需求。

1.2 策略模式

由於多種篩選條件的結果都是返回一個boolean值,那么可以把這個條件抽取出來,然后在篩選的時候傳入條件。這個篩選條件叫做謂詞

創建謂詞接口:

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

添加幾個判斷條件:

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

篩選的方法:

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

這樣,我們就可以根據不同的條件進行篩選了。


List<Apple> inventory = new ArrayList<>();
inventory.add(new Apple("red", 100));
inventory.add(new Apple("red", 200));
inventory.add(new Apple("green", 200));
List<Apple> redHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate());
Assert.assertEquals(1, redHeavyApples.size());
Assert.assertEquals(200, redHeavyApples.get(0).getWeight());

以上的代碼設計方案幾乎是最好理解和擴展的了,當條件發生改變的時候只要增加一個類就可以。但java8提供了更好的選擇,一種你只要聲明一個接口,具體實現不用管,只有當使用的時候才去關心。

1.3 方法傳遞

java8提供了把方法當做參數傳遞的能力。這樣,上面的代碼就可以這樣寫:

List<Apple> apples = filterApples(inventory, apple -> "red".equals(apple.getColor()) && apple.getWeight() > 150);
Assert.assertEquals(1, apples.size());
Assert.assertEquals(200, apples.get(0).getWeight());

除了接口聲明,不需要實現接口的類。我們只需要傳入一個類似匿名內部類的東西,是的,lambda表達式和匿名內部類是可以互相轉換的。

如此,我們設計接口的時候只要聲明一個接口作為參數,然后再調用的時候把邏輯當做參數傳進去。這個在我看來就是傳遞方法了。就像Javascript,可以把一個方法當做參數。

與之前的設計模式相比,lambda可以不用寫那么類。

1.4 新需求

現在,果農需要包裝蘋果。包裝的方式有多種,我將包裝的結果打印出來,就是打印的樣式也有多種。比如:

A light green apple

或者

An apple of 150g

上面是兩種打印方式,按照之前的策略模式需要創建兩個類。下面采用lambda來實現。

public interface AppleFormatter {
    String format(Apple apple);
}

public class AppleOutput{
    public static void prettyPrintApple(List<Apple> inventory, AppleFormatter formatter){
        for (Apple apple : inventory) {
            String format = formatter.format(apple);
            System.out.println(format);
        }
    }
    
    public static void main(String[] args){
        List<Apple> inventory = new ArrayList<>();
        inventory.add(new Apple("red", 100));
        inventory.add(new Apple("red", 200));
        inventory.add(new Apple("green", 200));

        prettyPrintApple(inventory, new AppleFormatter() {
            @Override
            public String format(Apple apple) {
                String characteristic = apple.getWeight()>150?"heavy":"light";
                return "A " + characteristic + " " + apple.getColor() + " apple.";
            }
        });

        prettyPrintApple(inventory, apple -> "An apple of " + apple.getWeight() + "g");

    }
}

控制台打印:

A light red apple.
A heavy red apple.
A heavy green apple.
An apple of 100g
An apple of 200g
An apple of 200g

如果使用IntelIJ IDEA作為編輯器,那么肯定會忍受不了匿名內部類,因為IDEA會不停的提示你:匿名內部類可以轉變為方法參數。

1.5 更普遍的用法

上面的篩選只是針對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<T>();
    for (T e : list) {
        if (p.test(e)){
            result.add(e);
        }
    }

    return result;
}

那么,可能這樣用:

public static void main(String[] args) {
    List<Apple> appleList = new ArrayList<>();
    appleList.add(new Apple("red", 100));
    appleList.add(new Apple("red", 160));
    appleList.add(new Apple("green", 60));

    List<Apple> redApples = filter(appleList, (Apple apple) -> "red".equals(apple.getColor()));
    Assert.assertEquals(2, redApples.size());

    List<Integer> numberList = Arrays.asList(1,2,3,4,5,6,7,8,9);
    List<Integer> lessThan4Numbers = filter(numberList, (Integer num) -> num < 4);
    Assert.assertEquals(3, lessThan4Numbers.size());

}

1.6 排序

行為參數化的過程掌握后,很多東西就會自然而然的使用了。比如排序。果農需要將蘋果按照大小排序呢?

java8中List是有默認方法的:

default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

其實就是將以前手動排序封裝了。那么,蘋果的排序就可以傳入一個比較器實現:

@Test
public void sort(){
    List<Apple> appleList = new ArrayList<>();
    appleList.add(new Apple("red", 100));
    appleList.add(new Apple("red", 160));
    appleList.add(new Apple("green", 60));
    
    appleList.sort((o1, o2) -> o1.getWeight()-o2.getWeight());
}

根據IDEA的提示,進一步:

appleList.sort(Comparator.comparingInt(Apple::getWeight));

這里就涉及了多次行為傳參了。后面再說。

1.7 Runnable

多線程Runnable的時候經常會采用匿名內部類的做法:

@Test
public void testRunnable(){
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("running");
        }
    };

    new Thread(runnable).start();
}

采用lambda行為傳參就變為:

@Test
public void testRunnable(){
    Runnable runnable = () -> System.out.println("running");

    new Thread(runnable).start();
}

小結

本次測試主要理解如下內容:

  • 行為參數化,就是一個方法接受多個不同的行為作為參數,並在內部使用它們,完成不同行為的能力。
  • 傳遞代碼,就是將行為作為參數傳遞給方法。

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM