Java工具類與函式程式設計毫不相干
最近,由於我把工具類看作反模式,所以被指責反對函數語言程式設計。這是絕對錯誤的!我認為它們是很糟糕的反模式,因為他們與函數語言程式設計無關。我認為其中有兩個基本原因。首先,函數語言程式設計是可宣告的,然而工具類方法是命令式的。第二,函數語言程式設計是基於lambda演算,即被傳遞引數的函式。從這個意義上來說,工具類方法不是函式。我會用一點時間來解釋一下。
在Java中,基本上有兩種被Guava、Apache Commons和其它開發庫推薦使用的拙劣的工具類。第一種是使用傳統的類,第二種就是Java 8的lambda。現在讓我們看看為什麼工具類和函數語言程式設計關係不大,以及錯誤觀念的來源。
這就是來源於Java 1.0中Math工具類的一個典型示例:
public class Math { public static double abs(double a); // a few dozens of other methods of the same style }
當你想要計算一個浮點型數字的絕對值,你可以使用如下方式:
double x = Math.abs(3.1415926d);
這裡有什麼問題呢?我們需要一個函式,並且我們從 Math類中得到了結果。這個類有許多有用的內建函式,可以用於許多典型的數學運算,比如計算最大值、最小值、正弦、餘弦等。這是一個非常流行的概念,許 多商業化或者開源產品也是如此。自從Java出現(Math類在Java首個版本被引入),這些工具類就被廣泛使用。當然,在技術上沒有什麼不妥。相反, 他們是命令式和過程式的。我們是否在意呢?這取決於你的選擇。讓我們來看看他們有什麼區別。
基本上有兩種不同的選擇,宣告式和命令式。
就改變程式狀態的宣告來說,指令式程式設計的重點是描述一個程式是如何運作的。我們剛剛看到了上面一個指令式程式設計的例子。下面是另一個(這是一個和麵向物件無關,純粹的命令式並且程式化的程式碼):
public class MyMath { public double f(double a, double b) { double max = Math.max(a, b); double x = Math.abs(max); return x; } }
就採取的一系列舉措來說,宣告式程式設計側重於在沒有規定如何做的情況下程式應該完成哪些事情。就像是Lisp中的程式碼,一種函數語言程式設計語言。
(defun f (a b) (abs (max a b)))
我們明白了什麼?只是句法的不同?不是這樣的。
在命令式和宣告式之間有很多描述差異,但是我儘量給出自己的理解。基本上有三種角色在使用f函式的場景下相互影響:買家、包裝者和消費者,讓我們談一談下面的呼叫:
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()是結果的包裝者,方法foo()是消費者。無論使用哪種程式設計風格,總是有這三個參與其中,買家、包裝者,和消費者。
想象一下,你是一個買家並希望購買禮物給你的女朋友或男朋友。首先會想到進一家店鋪,消費50美元,讓別人噴上香水打包給你,然後寄給你的朋友(回報是一枚香吻),這是命令式的風格。
第二個選項是進一家店鋪,消費50美元,並得到一張禮品券,你將此券展示給你的朋友(回報是一枚香吻)。當他或者她想要得到這股芳香,他或她就會進這家店來得到它。這就是宣告式風格。
看到什麼區別了麼?
在第一個場景中,這是命令式的風格,你要求包裝者(一家店鋪)使用庫存中的香水來打包,並作為準備好的禮品呈現給你。在第二個 場景中,這是宣告式的,你最終得到了店鋪的承諾,當必要的時候店鋪職員會找到香水來打包禮物,並提供給需要的人。如果你的朋友從來沒有進過有禮品券的這家 店,這股芳香將一直留在這家店中。
此外,你的朋友可以用這個禮品券當做這個禮品本身,就不用去這家店。他或她可能會將這張券作為禮物給其他人,或者用來交換其它禮券或者禮品。這個禮品券本身成為了一個禮品。
因此,區別就是消費者得到了什麼,是用來當做禮品(命令式)還是之後可以轉換成真實禮品的禮券(宣告式)。
工具類,就像從JDK中的Math類或 者Apache Commons中的StringUtils類中立刻得到了準備好的禮品。然而,從Lisp中的函式和其它函數語言程式設計中,卻得到了“禮券”。比如,如果你想 呼叫Lisp中的求最大值的方法,但只有當你真正開始使用的時候才能計算出來。
(let (x (max 1 5)) (print "X equals to " x))
直到輸出結果列印到螢幕上,求最大值的函式才會呼叫。當你嘗試去“購買”1到5之間最大值的時候,這個x就是一個返回給你的“禮券”。
但是請注意,巢狀的Java靜態函不會讓他們可宣告化,程式碼仍然是命令式的,因為此時方法進行了傳值。
public class MyMath { public double f(double a, double b) { return Math.abs(Math.max(a, b)); } }
你可能會說,“好吧,我明白了。但是為什麼宣告式的風格比命令式的更好呢?有什麼大不了的呢?”我會慢慢解釋的。首先讓我來展示在物件導向中函數語言程式設計中的函式和靜態方法的區別。正如上面所提到的,這是工具類和函數語言程式設計之間第二大的區別。
在函式式變成語言中,你可以這麼做:
(defun foo (x) (x 5))
然後,你可以呼叫這個x:
(defun bar (x) (+ x 1)) // defining function bar (print (foo bar)) // passing bar as an argument to foo
就函數語言程式設計而 言,Java中的靜態方法不是函式。你不能用一個靜態方法做這樣的事。你不能將一個靜態方法當做引數傳遞給其他方法。基本上靜態方法是生產者,或者簡單地說,Java由唯一的名字所宣告。唯一的方法就是呼叫一個程式並且傳遞所有必要的引數給它。這個程式將會計算出結果並立即返回給呼叫者。
現在,我們來到了最終的問題上,我能聽到你在問:“好吧,工具類不是函數語言程式設計,但是他們看起來很像函數語言程式設計,他們執行的很快,並且使用很方便。為什麼不用他們?為什麼當20年的Java歷史證明了工具類是每一個Java開發者的主要手段的時候,又要力求完美?”
除了物件導向的,這點我經常受指責,這裡有一些實際的原因(順便說一句,我推崇物件導向)。
可測試性。在工具類中呼叫靜態方法是硬編碼式的依賴,它不能因為測試的需要而被打斷。如果你的類正在呼叫FileUtils.readFile(),除非我的磁碟上有一個實際的檔案,否則我無法測試。
效率。工具類,由於其命令式的性質,比可替代的宣告式更加低效。即使當他們不是必要使用 的時候,他們也盲目地進行所有的計算,處理資源。而不是返回一個期望值來分隔字串chunks、StringUtils.split()可以立即打斷 它。同時,這也打破了所有可能的chunks,即使“買家”僅僅需要第一個。
可讀性。工具類往往 是龐大的(嘗試從Apache Commons閱讀StringUtils或者FileUtils的原始碼)。關注點分離可以使得物件導向如此優雅,但這些想法在工具類中是沒有的。他們盡 量把所有可能的程式放進一個.java檔案,這導致當它的大小超過了許多靜態方法的時候是極難維護的。
最後,我要重申一下:工具類與函式程式設計無關。他們僅僅是靜態方法的包裝,是命令式的程式。無論你要宣告他們多少次,他們有多渺小,都要儘量遠離他們而去使用可靠、健壯的物件。
相關文章
- 【JAVA程式設計】實驗三 函式與物件Java程式設計函式物件
- Scala函式與函數語言程式設計函式函數程式設計
- 函式響應式程式設計與RxSwift函式程式設計Swift
- 邏輯程式設計與函式程式設計的介紹程式設計函式
- Java程式設計基礎05——方法(函式)Java程式設計函式
- 函式程式設計函式程式設計
- Effective C++:類與函式的設計和申明C++函式
- 好程式設計師Java培訓分享Java程式設計師常用的工具類庫程式設計師Java
- 【C++ 泛型程式設計01:模板】函式模板與類别範本C++泛型程式設計函式
- Lambda表示式入門--函數語言程式設計與函式式介面函數程式設計函式
- scala 函式程式設計函式程式設計
- 函式程式設計之道函式程式設計
- 《高質量C++/C程式設計指南》第9章:類的建構函式、解構函式與賦值函式C++C程式程式設計函式賦值
- Java中的函數語言程式設計(二)函式式介面Functional InterfaceJava函數程式設計函式Function
- Kotlin 函式與函數語言程式設計淺析Kotlin函式函數程式設計
- JavaScript函數語言程式設計之pointfree與宣告式程式設計JavaScript函數程式設計
- 好程式設計師Java教程分享Java之包裝類與常用類程式設計師Java
- 高質量C++/C程式設計指南(第9章 類的建構函式、解構函式與賦值函式) (轉)C++C程式程式設計函式賦值
- Java 函數語言程式設計(二)Lambda表示式Java函數程式設計
- 好程式設計師Java培訓分享Java函式式編碼結構程式設計師Java函式
- 【趣解程式設計】函式程式設計函式
- Windows 程式設計常用函式Windows程式設計函式
- 《java併發程式設計的藝術》併發工具類Java程式設計
- python程式設計之slice與indices函式用法Python程式設計函式
- 03 shell程式設計之case語句與函式程式設計函式
- 物件導向與函式程式設計的比較物件函式程式設計
- Zepto 原始碼分析 3 - qsa 實現與工具函式設計原始碼函式
- 淺談函數語言程式設計與 Java Stream函數程式設計Java
- JS 命令式 宣告式 函式式 程式設計?JS函式程式設計
- 好程式設計師Python教程系列遞迴函式與匿名函式呼叫程式設計師Python遞迴函式
- Java中的七種函式程式設計技術 - foojayJava函式程式設計
- java8函數語言程式設計筆記-破壞式更新和函式式更新Java函數程式設計筆記函式
- 函數語言程式設計-鏈式程式設計RAC函數程式設計
- 不用任何賦值的程式設計稱為*函式式*程式設計賦值程式設計函式
- java抽象類與介面——設計模式Java抽象設計模式
- 揚帆起航:從指令式程式設計到函式響應式程式設計程式設計函式
- 前端基礎進階(七):函式與函數語言程式設計前端函式函數程式設計
- select函式socket程式設計函式程式設計