声明式编程与函数式编程_实用程序类与函数式编程无关
聲明式編程與函數式編程
最近,我被指控反對函數式編程,因為我將實用程序類稱為反模式 。 絕對是錯的! 好吧,我確實認為它們是一個糟糕的反模式,但是它們與函數式編程無關。 我相信有兩個基本原因。 首先,函數式編程是聲明性的,而實用程序類的方法則是必需的。 其次,函數式編程基于lambda演算,其中可以將函數分配給變量。 從這個意義上說,實用程序類方法不是函數。 我將在一分鐘內對這些語句進行解碼。
在Java中,對于Guava , Apache Commons等人積極推廣的這些丑陋的實用程序類,基本上有兩種有效的替代方法。 第一個是傳統類的使用,第二個是Java 8 lambda 。 現在,讓我們看看為什么實用程序類與函數式編程甚至不一樣,以及這種誤解來自何處。
彩色我庫布里克(2005)
這是來自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以來??(在Java的第一個版本中引入了Math類),這些實用程序類隨處可見。 好吧,從技術上講沒有錯。 該代碼將起作用。 但這不是面向對象的編程。 相反,它是必須的和程序的。 我們在乎嗎? 好吧,由您決定。 讓我們看看有什么區別。
基本上有兩種不同的方法:聲明式和命令式。
命令式編程的重點是描述一個程序在改變程序狀態的語句方面如何運作的 。 我們剛剛在上面看到了命令式編程的示例。 這是另一種(這是與OOP無關的純命令式/過程式編程):
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中調用max函數,則只有在您真正開始使用它時,才計算兩個數字之間的實際最大值:
(let (x (max 1 5))(print "X equals to " x))在此print實際開始將字符輸出到屏幕之前, max函數將不會被調用。 當您嘗試“購買” 1至5之間的最大值時,此x是返回給您的“憑證”。
但是請注意,將Java靜態函數一個嵌套到另一個嵌套并不能使它們具有聲明性。 該代碼仍然勢在必行,因為它的執行可以在此處和現在提供結果:
public class MyMath {public double f(double a, double b) {return Math.abs(Math.max(a, b));} }“好吧,”您可能會說,“我明白了,但是為什么聲明式風格比命令式風格更好? 有什么大不了的?” 我明白了。 首先讓我展示一下函數編程中的函數與OOP中的靜態方法之間的區別。 如上所述,這是實用程序類和函數式編程之間的第二大區別。
在任何函數式編程語言中,您都可以這樣做:
(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開發人員的主要工具時,為什么要追求完美?”
除了我經常被指控的OOP原教旨主義外,還有一些非常實際的原因(順便說一句,我是OOP原教旨主義者):
可測性 。 實用程序類中對靜態方法的調用是硬編碼的依賴項,出于測試目的,它們永遠不會被破壞。 如果您的班級正在調用FileUtils.readFile() ,那么在不使用磁盤上實際文件的情況下,我將永遠無法對其進行測試。
效率 。 實用程序類由于其命令性而比它們的聲明性替代方法效率低得多。 他們只是在此時此地進行所有計算,即使在沒有必要的情況下也占用處理器資源。 StringUtils.split()不會返回將字符串分解成塊的承諾,而是立即將其分解。 即使“買方”只要求第一個,它也將其分解為所有可能的塊。
可讀性 。 實用程序類往往很大(嘗試從Apache Commons讀取StringUtils或FileUtils的源代碼)。 實用程序類中缺少關注點分離的整個想法,這使OOP如此美觀。 他們只是將所有可能的過程放在一個巨大的.java文件中,當它超過十二種靜態方法時,該文件將變得絕對無法維護。
最后,讓我重申一下:實用程序類與函數式編程無關。 它們只是靜態方法的包,這是命令程序。 無論您要聲明多少個物體,又要縮小多少物體,都應盡量遠離它們并使用堅固的,有凝聚力的物體。
翻譯自: https://www.javacodegeeks.com/2015/03/utility-classes-have-nothing-to-do-with-functional-programming.html
聲明式編程與函數式編程
總結
以上是生活随笔為你收集整理的声明式编程与函数式编程_实用程序类与函数式编程无关的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓抽奖软件(安卓抽奖)
- 下一篇: 两车备案什么意思(两车备案)