最近,我被指责为反函数式编程,因为我将实用类视为一种反模式。这是绝对错误的!我认为它们是不好的反模式,因为它们与函数式编程无关。我认为这有两个基本原因。首先,函数式编程是声明式的,而实用方法是命令式的。其次,函数式编程基于lambda演算,传递参数的函数。从这个意义上说,实用程序类方法不是函数。我会花点时间解释一下。在Java中,Guava、ApacheCommons和其他开发库推荐的基本有两个很差的实用类。第一种是使用传统类,第二种是Java8的lambda。现在让我们看看为什么实用程序类与函数式编程关系不大,以及误解从何而来。这是Java1.0中Math工具类的一个典型例子:.绝对值(3.1415926d);这里有什么问题?我们需要一个函数,我们从Math类中得到结果。这个类有很多有用的内置函数,可以用于很多典型的数学运算,比如计算最大值、最小值、正弦、余弦等。这是一个非常流行的概念,许多商业或开源产品也是如此.自Java问世以来(最新版本的Java中引入了Math类),这些实用类得到了广泛的应用。当然,这在技术上没有任何问题。相反,它们是命令式的和程序性的。我们关心吗?这取决于您的选择。让我们看看它们有何不同。基本上有两种不同的选择,声明式和命令式。就改变程序状态的语句而言,命令式编程的要点是描述程序如何工作。我们刚刚在上面看到了一个命令式编程的例子。这是另一个(这是与面向对象无关的纯命令式和过程式代码):;returnx;}}作为一组主动性,声明式编程侧重于程序应该做什么而不指定如何做。这就像Lisp中的代码,一种函数式编程语言。(defunf(ab)(abs(maxab)))我们了解什么?只是语法上的差异?不是这样的。命令式和声明式之间有许多描述性差异,但我尝试给出自己的理解。基本上有三个角色在使用f函数的上下文中进行交互:买家,包装商和消费者,让我们谈谈下面的调用:publicvoidfoo(){doublex=this.calc(5,-7);System.out.println("max+absequalsto"+x);}privatedoublecalc(doublea,doubleb){doublex=Math.f(a,b);returnx;}在这个例子中,方法calc()是一个买家,方法Math.f()是结果的包装器,方法foo()是消费者。无论使用哪种编程风格,总是涉及到这三个方面,购买者、包装者和消费者。想象一下,您是买家,想为您的女朋友或男朋友买礼物。首先,你会想到进一家店,花50,请人给你喷香水,给你包装好,然后寄给你的朋友(回礼是甜蜜的吻),这是命令式的风格。第二种选择是去一家商店,花50美元,然后得到一张礼券给朋友看(以换取一个吻)。当他或她想要得到香水时,他或她会到这家店里去拿。这是声明式风格。你看到区别了吗?第一个场景,命令式,你让包装商(商店)用库存的香水包装好,作为现成的礼物送给你。在声明性的第二个场景中,你最终得到了商店的承诺,必要时店员会找到香水来包装礼物,送给需要的人。如果您的朋友从未带着礼券去过商店,香水将留在商店里。此外,您的朋友无需前往商店即可将此礼券作为礼物使用。他或她可以将代金券作为礼物送给其他人,或换取其他礼券或礼物。这张礼券本身就是一件礼物。因此,区别在于消费者得到了什么,无论是礼物(命令式)还是以后可以转换为真正礼物的礼券(声明式)。实用程序,如JDK中的Math类或ApacheCommons中的StringUtils类,可以立即获得现成的礼物。但是,从Lisp和其他函数式编程中的函数来看,有一个“礼券”。比如你想调用Lisp中的value方法,但是只有真正开始使用的时候才能计算出来。(let(x(max15))(print"Xequalsto"x))直到输出结果打印在屏幕上,才会调用求最大值的函数。当你试图“购买”一个介于1和5之间的值时,这个x就是退还给你的“礼券”。但是请注意,嵌套的Java静态函数不会使它们可声明,代码仍然是命令式的,因为此时该方法是按值传递的。publicclassMyMath{publicdoublef(doublea,doubleb){returnMath.abs(Math.max(a,b));}}你可能会说,“好吧,我明白了。但为什么声明式风格比命令式风格更好?有什么大不了的?交易?”我慢慢解释。首先让我展示一下面向对象的函数式编程中函数和静态方法之间的区别。如上所述,这是实用类和函数式编程之间的第二大区别。在函数式编程中,你可以这样做:(defunfoo(x)(x5))然后,你可以调用这个x:(defunbar(x)(+x1))//definingfunctionbar(print(foobar))//passingbarasanargumenttofoo静态方法就函数式编程而言,Java中的函数不是函数。你不能用静态方法做这样的事情。您不能将静态方法作为参数传递给另一个方法。基本上静态方法是一个生产者,或者简单地说,Java是由一个唯一的名称声明的。唯一的方法是调用程序并将所有必要的参数传递给它。该程序将计算结果并立即将其返回给调用者。现在,我们来到最后一个问题,我能听到你在问:“嗯,实用类不是FP,但它们看起来很像FP,它们速度快,而且易于使用。为什么不使用它们?当20年的Java历史证明实用程序类是每个Java开发人员的主要手段时,为什么还要追求最好?”除了我经常被指责的面向对象之外,还有一些实际原因(顺便说一下,我是面向对象的粉丝)。可测试性。在实用程序类中调用静态方法是一种硬编码的依赖关系,不能出于测试目的而破坏。如果您的类正在调用FileUtils.readFile(),除非磁盘上有实际文件,否则我无法进行测试。效率。实用程序类,由于其命令式的性质,不如声明性的替代方案有效。他们盲目地进行所有计算,处理资源,即使他们没有必要使用它们。StringUtils.split()可以立即打破它,而不是返回一个预期值来分隔字符串块。同时,这也打破了所有可能的块,即使“买家”只需要第一个。可读性。实用程序往往体积庞大(尝试从ApacheCommons阅读StringUtils或FileUtils的源代码)。关注点分离是面向对象如此优雅的原因,但这些想法在实用程序类中是不存在的。他们试图将所有可能的程序放在一个.java文件中,这使得当它的大小超过许多静态方法时维护起来非常困难。***,让我重申一下:实用类与函数式编程无关。它们只是静态方法的包装器,是命令式程序。无论您声明它们多少次,或者它们有多小,请尽量远离它们并使用可靠、健壮的对象。
