1. 程式人生 > >Java函數語言程式設計

Java函數語言程式設計

摘要: 在Java重構的過程中,巧妙的運用函式式思想能夠便捷地去掉重複。

函數語言程式設計是宣告式的。也就是說,她應該指定“什麼要做”而非“怎麼做”。這種方式使得我們可以工作更高的抽象層次。而傳統的過程式以及面向物件的語言,則是命令式的,因而更關注於“怎麼做”這個層面。站在面向物件思想的角度來看,函數語言程式設計將函式看成一等公民的思想,使得我們處理的粒度從類變小為函式,從而可以更好地滿足系統對重用性和擴充套件性的支援。也就是說,我們可以從函式的粒度,而非物件的粒度去思考領域問題。

例如,有這樣一個場景:我的業務模型是個Person,已經有業務介面能返回給我所有Person的list集合,我需要把根據一些條件返回符合對應關係的Person。

Person.java

public class Person {
    private String name;
    private String gender;
    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public
void setGender(String gender) { this.gender = gender; } public String getName() { return name; } public void setName(String name) { this.name = name; } }

首先,根據業務需求,寫出第一個方法 通過姓名找出符合的Person

PersonService.java

import java.util.ArrayList;
import java.util.List;

public
class PersonService { //假設這個list是通過已有介面返回的所有Person集合 private static List<Person> list = new ArrayList<Person>(); public static List<Person> findByName(String name){ List<Person> people = new ArrayList<Person>(); for (Person p : list){ if(name.equals(p.getName())){ people.add(p); } } return people; } }

接下來,第二個方法根據性別找出符合的Person

import java.util.ArrayList;
import java.util.List;

public class PersonService {
    //假設這個list是通過已有介面返回的所有Person集合
    private static List<Person> list = new ArrayList<Person>();

    public static List<Person> findByName(String name){
        List<Person> people = new ArrayList<Person>();
        for (Person p : list){
            if(name.equals(p.getName())){
                people.add(p);
            }
        }
        return people;
    }

    public static List<Person> findByGender(String gender){
        List<Person> people = new ArrayList<Person>();
        for (Person p : list){
            if(gender.equals(p.getGender())){
                people.add(p);
            }
        }
        return people;
    }
}

功能是實現了,但是要是這樣交差,估計得捱罵了,這麼多重複程式碼。仔細觀察剛新增的兩個方法。發現,除了下面兩行以外全都相同:
- if(name.equals(p.getName())){
- if(gender.equals(p.getGender())){

這裡的namegender是兩個字串引數,可以給它們用同樣的名字,比如str,這樣的話:
- if(str.equals(p.getName())){
- if(str.equals(p.getGender())){

這下,不同點就是getName()getGender(),是兩個方法,而Java是不能把函式當引數的,這樣重構起來就麻煩很多。
想到Java中還有一種迂迴的方式傳遞函式,那就是介面。
新建一個介面Criteria,在這個接口裡隱藏if條件語句裡面的實現細節。這樣就可以把findByName()findByGender高度抽象為一個方法find()

import java.util.ArrayList;
import java.util.List;

public class PersonService {
    //假設這個list是通過已有介面返回的所有Person集合
    private static List<Person> list = new ArrayList<Person>();

    public List<Person> findByName(String name) {
        return null;
    }

    public List<Person> findByGender(String gender) {
        return null;
    }

    public List<Person> find(Criteria criteria){
        List<Person> people = new ArrayList<Person>();
        for (Person p : list){
            if(criteria.matches(p)){
                people.add(p);
            }
        }
        return people;
    }
}

interface Criteria{
    boolean matches(Person person);

}

對於具體的findByName()方法,只需要實現匹配的細節即可:

public List<Person> findByName(String name) {
        return find(new Criteria() {
            @Override
            public boolean matches(Person person) {
                return person.getName().equals(name);
            }
        });
    }

經過改裝後的PersonService.java大致像下面這樣:

import java.util.ArrayList;
import java.util.List;

public class PersonService {
    //假設這個list是通過已有介面返回的所有Person集合
    private List<Person> list = new ArrayList<>();

    public List<Person> findByName(String name) {
        return find(new Criteria() {
            @Override
            public boolean matches(Person person) {
                return name.equals(person.getName());
            }
        });
    }

    public List<Person> findByGender(String gender) {
       return find(new Criteria() {
           @Override
           public boolean matches(Person person) {
               return gender.equals(person.getGender());
           }
       });
    }

    public List<Person> find(Criteria criteria){
        List<Person> people = new ArrayList<>();
        for (Person p : list){
            if(criteria.matches(p)){
                people.add(p);
            }
        }
        return people;
    }

}

interface Criteria{
    boolean matches(Person person);
}

看到這,估計有人要發飆了,說好的重構去重複,結果為了一行程式碼,增加了無數行無關程式碼,還無故多了一個類(介面Criteria)。原始程式碼才25行左右,現在將近40行程式碼。這個findByName()findByGender方法除了return後面的不同,剩下的完全一樣,雖然那些程式碼都是IDE自動生成的,但是看起來,這次重構沒有起到簡化程式碼的目的。甚至,如果你沒有用Java8,那上面的程式碼需要改動才能編譯通過。
當然,如果使用過java8,那麼應該也聽說過Lambda表示式。如果使用的IDE是Intellij的話,那應該慶幸一下,因為它已經識別到了需要改進的地方。
這裡寫圖片描述
這樣,看起來,程式碼會整潔許多:

import java.util.ArrayList;
import java.util.List;

public class PersonService {
    //假設這個list是通過已有介面返回的所有Person集合
    private List<Person> list = new ArrayList<>();

    public List<Person> findByName(String name) {
        return find(p-> name.equals(p.getName()));
    }

    public List<Person> findByGender(String gender) {
       return find(p-> gender.equals(p.getGender()));
    }

    public List<Person> find(Criteria criteria){
        List<Person> people = new ArrayList<>();
        for (Person p : list){
            if(criteria.matches(p)){
                people.add(p);
            }
        }
        return people;
    }

}

interface Criteria{
    boolean matches(Person person);
}

我定義了一個看起來多餘,但是又必須存在的介面Criteria。如果專案中有很多需要這樣重構的程式碼,不是得寫很多類似的介面?當然,JDK的設計者已經想到了這一點,於是就有了一個公用的介面Predicate。這樣,再整理一下程式碼:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class PersonService {
    //假設這個list是通過已有介面返回的所有Person集合
    private List<Person> list = new ArrayList<>();

    public List<Person> findByName(String name) {
        return find(p-> name.equals(p.getName()));
    }

    public List<Person> findByGender(String gender) {
       return find(p-> gender.equals(p.getGender()));
    }

    public List<Person> find(Predicate<Person> predicate){
        List<Person> people = new ArrayList<>();
        for (Person p : list){
            if(predicate.test(p)){
                people.add(p);
            }
        }
        return people;
    }
}

如果覺得這裡的find寫的臃腫,也可以改進:
這裡寫圖片描述

整理後的程式碼:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class PersonService {
    //假設這個list是通過已有介面返回的所有Person集合
    private List<Person> list = new ArrayList<>();

    public List<Person> findByName(String name) {
        return find(p -> name.equals(p.getName()));
    }

    public List<Person> findByGender(String gender) {
       return find(p -> gender.equals(p.getGender()));
    }

    public List<Person> find(Predicate<Person> predicate){
        return list.stream().filter(p -> predicate.test(p)).collect(Collectors.toList());
    }
}

這樣看起來就比剛開始複製/貼上的程式碼清爽許多。如果第三個業務需求是根據年齡找出符合條件的Person,你可以這樣寫:

public List<Person> findByAge(int age){
        List<Person> people = new ArrayList<>();
        for (Person person : list) {
            if(age == person.getAge()){
                people.add(person);
            }
        }
     return people;
}

中規中矩,沒有問題,但是如果寫成下面這樣,是不是更容易抓住方法的要點:

public List<Person> findByAge(int age){
     return find(p -> age == p.getAge());
}

剛開始重構花了很多時間,但是現在要新新增業務方法,我只需要一行程式碼:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class PersonService {
    //假設這個list是通過已有介面返回的所有Person集合
    private List<Person> list = new ArrayList<>();

    public List<Person> findByName(String name) {
        return find(p -> name.equals(p.getName()));
    }

    public List<Person> findByGender(String gender) {
       return find(p -> gender.equals(p.getGender()));
    }
    //根據年齡找出符合條件的Person
    public List<Person> findByAge(int age){
        return find(p -> age == p.getAge());
    }

    public List<Person> find(Predicate<Person> predicate){
        return list.stream().filter(p -> predicate.test(p)).collect(Collectors.toList());
    }
}

之後又有一個新的業務需求找出在給定年齡段的Person,依然只需要一行程式碼:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class PersonService {
    //假設這個list是通過已有介面返回的所有Person集合
    private List<Person> list = new ArrayList<>();

    public List<Person> findByName(String name) {
        return find(p -> name.equals(p.getName()));
    }

    public List<Person> findByGender(String gender) {
       return find(p -> gender.equals(p.getGender()));
    }

    public List<Person> findByAge(int age){
        return find(p -> age == p.getAge());
    }
    //找出在給定年齡段的Person
    public List<Person> findByAgeRange(int startAge,int endAge){
        return find(p -> startAge <= p.getAge() && endAge >= p.getAge());
    }

    public List<Person> find(Predicate<Person> predicate){
        return list.stream().filter(p -> predicate.test(p)).collect(Collectors.toList());
    }
}