我還記得在了解到FP以上的各種好處后想到:“這些優(yōu)勢(shì)都很吸引人,可是,如果必須非要用這種所有變量都是final的蹩腳語(yǔ)言,估計(jì)還是不怎么實(shí)用吧”。其實(shí)這樣的想法是不對(duì)的。對(duì)于Java這樣的指令式語(yǔ)言來(lái)說(shuō),如果所有的變量都是必須是final的,那么確實(shí)很束手束腳。然而對(duì)函數(shù)式語(yǔ)言來(lái)說(shuō),情況就不一樣了。函數(shù)式語(yǔ)言提供了一種特別的抽象工具,這種工具將幫助使用者編寫(xiě)FP代碼,讓他們甚至都沒(méi)想到要修改變量的值。高階函數(shù)就是這種工具之一。
FP語(yǔ)言中的函數(shù)有別于Java或是C??梢哉f(shuō)這種函數(shù)是一個(gè)全集:Java函數(shù)可以做到的它都能做,同時(shí)它還有更多的能力。首先,像在C里寫(xiě)程序那樣創(chuàng)建一個(gè)函數(shù):
int add(int i, int j) {
return i + j;
}
看起來(lái)和C程序沒(méi)什么區(qū)別,但是很快你就可以看出區(qū)別來(lái)。接下來(lái)我們擴(kuò)展Java的編譯器以便支持這種代碼,也就是說(shuō),當(dāng)我們寫(xiě)下以上的程序編譯器會(huì)把它轉(zhuǎn)化成下面的Java程序(別忘了,所有的變量都是final的):
class add_function_t {
int add(int i, int j) {
return i + j;
}
}
add_function_t add = new add_function_t();
在這里,符號(hào)add并不是一個(gè)函數(shù),它是只有一個(gè)函數(shù)作為其成員的簡(jiǎn)單的類(lèi)。這樣做有很多好處,可以在程序中把a(bǔ)dd當(dāng)成參數(shù)傳給其他的函數(shù),也可以把a(bǔ)dd賦給另外一個(gè)符號(hào),還可以在運(yùn)行時(shí)創(chuàng)建add_function_t的實(shí)例然后在不再需要這些實(shí)例的時(shí)候由系統(tǒng)回收機(jī)制處理掉。這樣做使得函數(shù)成為和integer或是string這樣的第一類(lèi)對(duì)象。對(duì)其他函數(shù)進(jìn)行操作(比如說(shuō)把這些函數(shù)當(dāng)成參數(shù))的函數(shù),就是所謂的高階函數(shù)。別讓這個(gè)看似高深的名字嚇倒你(譯者:好死不死起個(gè)這個(gè)名字,初一看還準(zhǔn)備搬出已經(jīng)塵封的高數(shù)教材……),它和Java中操作其他類(lèi)(也就是把一個(gè)類(lèi)實(shí)例傳給另外的類(lèi))的類(lèi)沒(méi)有什么區(qū)別??梢苑Q(chēng)這樣的類(lèi)為“高階類(lèi)”,但是沒(méi)人會(huì)在意,因?yàn)镴ava圈里就沒(méi)有什么很強(qiáng)的學(xué)術(shù)社團(tuán)。(譯者:這是高級(jí)黑嗎?)
那么什么時(shí)候該用高階函數(shù),又怎樣用呢?我很高興有人問(wèn)這個(gè)問(wèn)題。設(shè)想一下,你寫(xiě)了一大堆程序而不考慮什么類(lèi)結(jié)構(gòu)設(shè)計(jì),然后發(fā)現(xiàn)有一部分代碼重復(fù)了幾次,于是你就會(huì)把這部分代碼獨(dú)立出來(lái)作為一個(gè)函數(shù)以便多次調(diào)用(所幸學(xué)校里至少會(huì)教這個(gè))。如果你發(fā)現(xiàn)這個(gè)函數(shù)里有一部分邏輯需要在不同的情況下實(shí)現(xiàn)不同的行為,那么你可以把這部分邏輯獨(dú)立出來(lái)作為一個(gè)高階函數(shù)。搞暈了?下面來(lái)看看我工作中的一個(gè)真實(shí)的例子。
假設(shè)有一段Java的客戶(hù)端程序用來(lái)接收消息,用各種方式對(duì)消息做轉(zhuǎn)換,然后發(fā)給一個(gè)服務(wù)器。
class MessageHandler {
void handleMessage(Message msg) {
// ...
msg.setClientCode("ABCD_123");
// ...
sendMessage(msg);
}
// ...
}
再進(jìn)一步假設(shè),整個(gè)系統(tǒng)改變了,現(xiàn)在需要發(fā)給兩個(gè)服務(wù)器而不再是一個(gè)了。系統(tǒng)其他部分都不變,唯獨(dú)客戶(hù)端的代碼需要改變:額外的那個(gè)服務(wù)器需要用另外一種格式發(fā)送消息。應(yīng)該如何處理這種情況呢?我們可以先檢查一下消息要發(fā)送到哪里,然后選擇相應(yīng)的格式把這個(gè)消息發(fā)出去:
class MessageHandler {
void handleMessage(Message msg) {
// ...
if(msg.getDestination().equals("server1") {
msg.setClientCode("ABCD_123");
} else {
msg.setClientCode("123_ABC");
}
// ...
sendMessage(msg);
}
// ...
}
可是這樣的實(shí)現(xiàn)是不具備擴(kuò)展性的。如果將來(lái)需要增加更多的服務(wù)器,上面函數(shù)的大小將呈線性增長(zhǎng),使得維護(hù)這個(gè)函數(shù)最終變成一場(chǎng)噩夢(mèng)。面向?qū)ο蟮木幊谭椒ǜ嬖V我們,可以把MessageHandler變成一個(gè)基類(lèi),然后將針對(duì)不同格式的消息編寫(xiě)相應(yīng)的子類(lèi)。
abstract class MessageHandler {
void handleMessage(Message msg) {
// ...
msg.setClientCode(getClientCode());
// ...
sendMessage(msg);
}
abstract String getClientCode();
// ...
}
class MessageHandlerOne extends MessageHandler {
String getClientCode() {
return "ABCD_123";
}
}
class MessageHandlerTwo extends MessageHandler {
String getClientCode() {
return "123_ABCD";
}
}
這樣一來(lái)就可以為每一個(gè)接收消息的服務(wù)器生成一個(gè)相應(yīng)的類(lèi)對(duì)象,添加服務(wù)器就變得更加容易維護(hù)了。可是,這一個(gè)簡(jiǎn)單的改動(dòng)引出了很多的代碼。僅僅是為了支持不同的客戶(hù)端行為代碼,就要定義兩種新的類(lèi)型!現(xiàn)在來(lái)試試用我們剛才改造的語(yǔ)言來(lái)做同樣的事情,注意,這種語(yǔ)言支持高階函數(shù):
class MessageHandler {
void handleMessage(Message msg, Function getClientCode) {
// ...
Message msg1 = msg.setClientCode(getClientCode());
// ...
sendMessage(msg1);
}
// ...
}
String getClientCodeOne() {
return "ABCD_123";
}
String getClientCodeTwo() {
return "123_ABCD";
}
MessageHandler handler = new MessageHandler();
handler.handleMessage(someMsg, getClientCodeOne);
在上面的程序里,我們沒(méi)有創(chuàng)建任何新的類(lèi)型或是多層類(lèi)的結(jié)構(gòu)。僅僅是把相應(yīng)的函數(shù)作為參數(shù)進(jìn)行傳遞,就做到了和用面向?qū)ο缶幊桃粯拥氖虑?,而且還有額外的好處:一是不再受限于多層類(lèi)的結(jié)構(gòu)。這樣做可以做運(yùn)行時(shí)傳遞新的函數(shù),可以在任何時(shí)候改變這些函數(shù),而且這些改變不僅更加精準(zhǔn)而且觸碰的代碼更少。這種情況下編譯器其實(shí)就是在替我們編寫(xiě)面向?qū)ο蟮摹罢澈稀贝a(譯者:又稱(chēng)膠水代碼,粘接代碼)!除此之外我們還可以享用FP編程的其他所有優(yōu)勢(shì)。函數(shù)式編程能提供的抽象服務(wù)還遠(yuǎn)不止于此。高階函數(shù)只不過(guò)是個(gè)開(kāi)始。
更多建議: