工具類與函數(shù)編程毫不相干
最近,由于我把工具類看作反模式,所以被指責(zé)反對函數(shù)式編程。這是絕對錯誤的!我認(rèn)為它們是很糟糕的反模式,因?yàn)樗麄兣c函數(shù)式編程無關(guān)。我認(rèn)為其中有兩個基本原因。首先,函數(shù)式編程是可聲明的,然而工具類方法是命令式的。第二,函數(shù)式編程是基于lambda演算,即被傳遞參數(shù)的函數(shù)。從這個意義上來說,工具類方法不是函數(shù)。我會用一點(diǎn)時間來解釋一下。
在Java中,基本上有兩種被Guava、Apache Commons和其它開發(fā)庫推薦使用的拙劣的工具類。***種是使用傳統(tǒng)的類,第二種就是Java 8的lambda?,F(xiàn)在讓我們看看為什么工具類和函數(shù)式編程關(guān)系不大,以及錯誤觀念的來源。
這就是來源于Java 1.0中Math工具類的一個典型示例:
- public class Math {
- public static double abs(double a);
- // a few dozens of other methods of the same style
- }
當(dāng)你想要計算一個浮點(diǎn)型數(shù)字的絕對值,你可以使用如下方式:
- double x = Math.abs(3.1415926d);
這里有什么問題呢?我們需要一個函數(shù),并且我們從 Math類中得到了結(jié)果。這個類有許多有用的內(nèi)置函數(shù),可以用于許多典型的數(shù)學(xué)運(yùn)算,比如計算***值、最小值、正弦、余弦等。這是一個非常流行的概念,許 多商業(yè)化或者開源產(chǎn)品也是如此。自從Java出現(xiàn)(Math類在Java***版本被引入),這些工具類就被廣泛使用。當(dāng)然,在技術(shù)上沒有什么不妥。相反, 他們是命令式和過程式的。我們是否在意呢?這取決于你的選擇。讓我們來看看他們有什么區(qū)別。
基本上有兩種不同的選擇,聲明式和命令式。
就改變程序狀態(tài)的聲明來說,命令式編程的重點(diǎn)是描述一個程序是如何運(yùn)作的。我們剛剛看到了上面一個命令式編程的例子。下面是另一個(這是一個和面向?qū)ο鬅o關(guān),純粹的命令式并且程序化的代碼):
- public class MyMath {
- public double f(double a, double b) {
- double max = Math.max(a, b);
- double x = Math.abs(max);
- return x;
- }
- }
就采取的一系列舉措來說,聲明式編程側(cè)重于在沒有規(guī)定如何做的情況下程序應(yīng)該完成哪些事情。就像是Lisp中的代碼,一種函數(shù)式編程語言。
- (defun f (a b) (abs (max a b)))
我們明白了什么?只是句法的不同?不是這樣的。
在命令式和聲明式之間有很多描述差異,但是我盡量給出自己的理解?;旧嫌腥N角色在使用f函數(shù)的場景下相互影響:買家、包裝者和消費(fèi)者,讓我們談一談下面的調(diào)用:
- public void foo() {
- double x = this.calc(5, -7);
- System.out.println("max+abs equals to " + x);
- }
- private double calc(double a, double b) {
- double x = Math.f(a, b);
- return x;
- }
這個例子中,方法calc()是一個買家,方法Math.f()是結(jié)果的包裝者,方法foo()是消費(fèi)者。無論使用哪種編程風(fēng)格,總是有這三個參與其中,買家、包裝者,和消費(fèi)者。
想象一下,你是一個買家并希望購買禮物給你的女朋友或男朋友。首先會想到進(jìn)一家店鋪,消費(fèi)50美元,讓別人噴上香水打包給你,然后寄給你的朋友(回報是一枚香吻),這是命令式的風(fēng)格。
第二個選項(xiàng)是進(jìn)一家店鋪,消費(fèi)50美元,并得到一張禮品券,你將此券展示給你的朋友(回報是一枚香吻)。當(dāng)他或者她想要得到這股芳香,他或她就會進(jìn)這家店來得到它。這就是聲明式風(fēng)格。
看到什么區(qū)別了么?
在***個場景中,這是命令式的風(fēng)格,你要求包裝者(一家店鋪)使用庫存中的香水來打包,并作為準(zhǔn)備好的禮品呈現(xiàn)給你。在第二個 場景中,這是聲明式的,你最終得到了店鋪的承諾,當(dāng)必要的時候店鋪職員會找到香水來打包禮物,并提供給需要的人。如果你的朋友從來沒有進(jìn)過有禮品券的這家 店,這股芳香將一直留在這家店中。
此外,你的朋友可以用這個禮品券當(dāng)做這個禮品本身,就不用去這家店。他或她可能會將這張券作為禮物給其他人,或者用來交換其它禮券或者禮品。這個禮品券本身成為了一個禮品。
因此,區(qū)別就是消費(fèi)者得到了什么,是用來當(dāng)做禮品(命令式)還是之后可以轉(zhuǎn)換成真實(shí)禮品的禮券(聲明式)。
工具類,就像從JDK中的Math類或 者Apache Commons中的StringUtils類中立刻得到了準(zhǔn)備好的禮品。然而,從Lisp中的函數(shù)和其它函數(shù)式編程中,卻得到了“禮券”。比如,如果你想 調(diào)用Lisp中的求***值的方法,但只有當(dāng)你真正開始使用的時候才能計算出來。
- (let (x (max 1 5))
- (print "X equals to " x))
直到輸出結(jié)果打印到屏幕上,求***值的函數(shù)才會調(diào)用。當(dāng)你嘗試去“購買”1到5之間***值的時候,這個x就是一個返回給你的“禮券”。
但是請注意,嵌套的Java靜態(tài)函不會讓他們可聲明化,代碼仍然是命令式的,因?yàn)榇藭r方法進(jìn)行了傳值。
- public class MyMath {
- public double f(double a, double b) {
- return Math.abs(Math.max(a, b));
- }
- }
你可能會說,“好吧,我明白了。但是為什么聲明式的風(fēng)格比命令式的更好呢?有什么大不了的呢?”我會慢慢解釋的。首先讓我來展示在面向?qū)ο笾泻瘮?shù)式編程中的函數(shù)和靜態(tài)方法的區(qū)別。正如上面所提到的,這是工具類和函數(shù)式編程之間第二大的區(qū)別。
在函數(shù)式變成語言中,你可以這么做:
- (defun foo (x) (x 5))
然后,你可以調(diào)用這個x:
- (defun bar (x) (+ x 1)) // defining function bar
- (print (foo bar)) // passing bar as an argument to foo
就函數(shù)式編程而 言,Java中的靜態(tài)方法不是函數(shù)。你不能用一個靜態(tài)方法做這樣的事。你不能將一個靜態(tài)方法當(dāng)做參數(shù)傳遞給其他方法?;旧响o態(tài)方法是生產(chǎn)者,或者簡單地 說,Java由唯一的名字所聲明。唯一的方法就是調(diào)用一個程序并且傳遞所有必要的參數(shù)給它。這個程序?qū)嬎愠鼋Y(jié)果并立即返回給調(diào)用者。
現(xiàn)在,我們來到了最終的問題上,我能聽到你在問:“好吧,工具類不是函數(shù)式編程,但是他們看起來很像函數(shù)式編程,他們運(yùn)行的很快,并且使用很方便。為什么不用他們?為什么當(dāng)20年的Java歷史證明了工具類是每一個Java開發(fā)者的主要手段的時候,又要力求***?”
除了面向?qū)ο蟮?,這點(diǎn)我經(jīng)常受指責(zé),這里有一些實(shí)際的原因(順便說一句,我推崇面向?qū)ο螅?/p>
可測試性。在工具類中調(diào)用靜態(tài)方法是硬編碼式的依賴,它不能因?yàn)闇y試的需要而被打斷。如果你的類正在調(diào)用FileUtils.readFile(),除非我的磁盤上有一個實(shí)際的文件,否則我無法測試。
效率。工具類,由于其命令式的性質(zhì),比可替代的聲明式更加低效。即使當(dāng)他們不是必要使用 的時候,他們也盲目地進(jìn)行所有的計算,處理資源。而不是返回一個期望值來分隔字符串chunks、StringUtils.split()可以立即打斷 它。同時,這也打破了所有可能的chunks,即使“買家”僅僅需要***個。
可讀性。工具類往往 是龐大的(嘗試從Apache Commons閱讀StringUtils或者FileUtils的源碼)。關(guān)注點(diǎn)分離可以使得面向?qū)ο笕绱藘?yōu)雅,但這些想法在工具類中是沒有的。他們盡 量把所有可能的程序放進(jìn)一個.java文件,這導(dǎo)致當(dāng)它的大小超過了許多靜態(tài)方法的時候是極難維護(hù)的。
***,我要重申一下:工具類與函數(shù)編程無關(guān)。他們僅僅是靜態(tài)方法的包裝,是命令式的程序。無論你要聲明他們多少次,他們有多渺小,都要盡量遠(yuǎn)離他們而去使用可靠、健壯的對象。