实用程序类与函数式编程无关
最近,我被指控反對函數(shù)式編程,因?yàn)槲覍?shí)用程序類稱為反模式 。 絕對是錯(cuò)的! 好吧,我確實(shí)認(rèn)為它們是一種糟糕的反模式,但是它們與函數(shù)式編程無關(guān)。 我相信有兩個(gè)基本原因。 首先,函數(shù)式編程是聲明性的,而實(shí)用程序類的方法則是必不可少的。 其次,函數(shù)式編程基于lambda演算,其中可以將函數(shù)分配給變量。 從這個(gè)意義上說,實(shí)用程序類方法不是函數(shù)。 我將在一分鐘內(nèi)解碼這些語句。
在Java中,對于Guava , Apache Commons等人積極推廣的這些丑陋的實(shí)用程序類,基本上有兩種有效的替代方法。 第一個(gè)是傳統(tǒng)類的使用,第二個(gè)是Java 8 lambda 。 現(xiàn)在,讓我們看看為什么實(shí)用程序類與函數(shù)式編程甚至不一樣,以及這種誤解來自何處。
彩色我?guī)觳祭锟?#xff08;2005):布萊恩·庫克(Brian W. Cook)
這是來自Java 1.0的實(shí)用程序類Math的典型示例:
public class Math {public static double abs(double a);// a few dozens of other methods of the same style }當(dāng)您要計(jì)算浮點(diǎn)數(shù)的絕對值時(shí),將使用以下方法:
double x = Math.abs(3.1415926d);它出什么問題了? 我們需要一個(gè)函數(shù),并且可以從Math類中獲得它。 該類內(nèi)部有許多有用的函數(shù),可用于許多典型的數(shù)學(xué)運(yùn)算,例如計(jì)算最大值,最小值,正弦,余弦等。 只看任何商業(yè)或開源產(chǎn)品。 自發(fā)明Java以來??,這些實(shí)用程序類到處都有使用(此Math類是在Java的第一個(gè)版本中引入的)。 好吧,從技術(shù)上講沒有錯(cuò)。 該代碼將起作用。 但這不是面向?qū)ο蟮木幊獭?相反,它是必須的和程序的。 我們在乎嗎? 好吧,由您決定。 讓我們看看有什么區(qū)別。
基本上有兩種不同的方法:聲明式和命令式。
命令式編程的重點(diǎn)是描述一個(gè)程序在改變程序狀態(tài)的語句方面如何運(yùn)作的 。 我們剛剛在上面看到了命令式編程的示例。 這是另一個(gè)(這是與OOP無關(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;} }聲明式編程的重點(diǎn)是程序應(yīng)該完成什么不規(guī)定如何做到這一點(diǎn)的動(dòng)作序列方面采取。 這就是在功能編程語言Lisp中相同代碼的外觀:
(defun f (a b) (abs (max a b)))有什么收獲? 語法不同嗎? 并不是的。
命令式和聲明式之間的區(qū)別有很多定義 ,但是我會(huì)盡力而為。 在此方案中,與該f函數(shù)/方法交互的角色基本上有三個(gè): 買主 ,結(jié)果打包者和結(jié)果消費(fèi)者 。 假設(shè)我這樣調(diào)用此函數(shù):
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)格,總有以下三個(gè)人參與:購買者,包裝者和消費(fèi)者。
假設(shè)您是買家,并且想為您的(女友)朋友購買禮物。 第一種選擇是去一家商店,支付50美元,讓他們?yōu)槟b香水,然后將其交付給朋友(并得到一個(gè)吻)。 這是當(dāng)務(wù)之急的風(fēng)格。
第二種選擇是去一家商店,支付50美元,并獲得一張禮品卡。 然后,您將此卡片出示給朋友(并得到一個(gè)吻)。 當(dāng)他或她決定將其轉(zhuǎn)換為香水時(shí),他或她將前往商店并購買。 這是一種聲明式樣式。
看到不同?
在第一種情況下,當(dāng)務(wù)之急是,您迫使包裝商(一家美容店)找到庫存的香水,將其包裝,然后將其作為即用型產(chǎn)品展示給您。 在第二種情況下,這是聲明性的,您只是從商店那里得到了一個(gè)承諾,即最終,在必要時(shí),工作人員會(huì)找到庫存的香水,將其包裝,然后提供給需要的人。 如果您的朋友從不使用該禮品卡去商店,則香水將保留庫存。
而且,您的朋友可以將該禮品卡用作產(chǎn)品本身,而無需訪問商店。 他或她可以代之以將其作為禮物贈(zèng)送給其他人,或僅將其換成另一張卡或產(chǎn)品。 禮品卡本身就是產(chǎn)品!
因此,區(qū)別在于消費(fèi)者所得到的是-可以使用的產(chǎn)品(必須)或該產(chǎn)品的憑證,可以在以后將其轉(zhuǎn)換為真實(shí)的產(chǎn)品(說明性)。
實(shí)用程序類(例如JDK中的Math或Apache Commons中的StringUtils返回準(zhǔn)備立即使用的產(chǎn)品,而Lisp和其他功能語言中的函數(shù)返回“憑單”。 例如,如果您在Lisp中調(diào)用max函數(shù),則只有在您實(shí)際開始使用它時(shí),才計(jì)算兩個(gè)數(shù)字之間的實(shí)際最大值:
(let (x (max 1 5))(print "X equals to " x))在此print實(shí)際開始將字符輸出到屏幕之前, max函數(shù)將不會(huì)被調(diào)用。 x是您嘗試“購買” 1至5之間的最大值時(shí)返回給您的“憑單”。
但是請注意,將Java靜態(tài)函數(shù)一個(gè)嵌套到另一個(gè)嵌套并不能使它們具有聲明性。 該代碼仍然勢在必行,因?yàn)樗膱?zhí)行可以在此處和現(xiàn)在提供結(jié)果:
public class MyMath {public double f(double a, double b) {return Math.abs(Math.max(a, b));} }“好吧,”您可能會(huì)說,“我明白了,但是為什么聲明式風(fēng)格比命令式風(fēng)格更好? 有什么大不了的?” 我明白了。 首先讓我展示一下函數(shù)編程中的函數(shù)與OOP中的靜態(tài)方法之間的區(qū)別。 如上所述,這是實(shí)用程序類和函數(shù)式編程之間的第二大區(qū)別。
在任何函數(shù)式編程語言中,您都可以這樣做:
(defun foo (x) (x 5))然后,稍后可以將其稱為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)方法執(zhí)行任何此類操作。 您可以將靜態(tài)方法作為參數(shù)傳遞給另一個(gè)方法。 基本上,靜態(tài)方法是過程,或者簡而言之,是以唯一名稱分組的Java語句。 訪問它們的唯一方法是調(diào)用過程并將所有必要的參數(shù)傳遞給該過程。 該過程將計(jì)算出一些內(nèi)容并返回立即可以使用的結(jié)果。
現(xiàn)在我們可以聽到最后一個(gè)問題,我可以聽到你問:“好吧,實(shí)用程序類不是函數(shù)式編程,但是它們看起來像函數(shù)式編程,它們運(yùn)行非常快,并且非常易于使用。 為什么不使用它們? 當(dāng)20年的Java歷史證明實(shí)用程序類是每個(gè)Java開發(fā)人員的主要工具時(shí),為什么要追求完美?”
除了我經(jīng)常被指控的OOP原教旨主義外,還有一些非常實(shí)際的原因(順便說一句,我是OOP原教旨主義者):
可測性 。 對實(shí)用程序類中的靜態(tài)方法的調(diào)用是硬編碼的依賴項(xiàng),出于測試目的,它們永遠(yuǎn)不會(huì)被破壞。 如果您的班級正在調(diào)用FileUtils.readFile() ,那么在不使用磁盤上實(shí)際文件的情況下,我將永遠(yuǎn)無法對其進(jìn)行測試。
效率 。 實(shí)用程序類由于其強(qiáng)制性而比它們的聲明性替代方法效率低得多。 他們只是在此時(shí)此地進(jìn)行所有計(jì)算,即使在沒有必要的情況下也占用處理器資源。 StringUtils.split()不會(huì)返回將字符串分解成塊的承諾,而是立即將其分解。 即使“買方”只要求第一個(gè),它也將其分解為所有可能的塊。
可讀性 。 實(shí)用程序類往往很大(嘗試從Apache Commons讀取StringUtils或FileUtils的源代碼)。 實(shí)用程序類中缺少關(guān)注點(diǎn)分離的整個(gè)想法,這使OOP如此美觀。 他們只是將所有可能的過程放入一個(gè)巨大的.java文件中,當(dāng)它超過十二種靜態(tài)方法時(shí),該文件將變得絕對無法維護(hù)。
最后,讓我重申一下:實(shí)用程序類與函數(shù)式編程無關(guān)。 它們只是靜態(tài)方法的包,這是命令程序。 無論您要聲明多少個(gè)物體,又要縮小多少物體,都應(yīng)盡量遠(yuǎn)離它們并使用堅(jiān)固的,有凝聚力的物體。
翻譯自: https://www.javacodegeeks.com/2015/03/utility-classes-have-nothing-to-do-with-functional-programming.html
總結(jié)
以上是生活随笔為你收集整理的实用程序类与函数式编程无关的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 休眠锁定模式– PESSIMISTIC_
- 下一篇: 携程优惠券用不了解决方法(携程优惠券用不