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())){
這裡的name
和gender
是兩個字串引數,可以給它們用同樣的名字,比如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());
}
}