chengaofeng
发布于 2024-08-22 / 21 阅读
0
0

函数式编程与面向对象编程

  1. 面向对象编程通过封装不确定因素来使代码能被人理解;函数式编程通过尽量减少不确定因素来使代码能被人理解。

  2. Java接管内存分配减轻了我们的负担,函数式编程语言让我们用高阶抽象从容取代基本的控制结构,也有着同样的意义。“将琐碎的细节交托给运行时,令繁冗的实现化作轻巧”

  3. 交织”(complect):穿插缠绕地合为一体,使错综复杂。命令式编程风格常常迫使我们出于性能考虑,把不同的任务交织起来,以便能够用一次循环来完成多个任务。而函数式编程用map()、filter()这些高阶函数把我们解放出来,让我们站在更高的抽象层次上去考虑问题,把问题看得更清楚。

  4. 封装、作用域、可见性等面向对象编程(OOP)构造,这些机制的存在意义,都是为了精细地控制谁能够感知状态和改变状态。而当涉及多线程的时候,对状态的控制就更复杂了。

  5. 大多数函数式语言在这个问题上采取了另一种做法,它们认为,与其建立种种机制来控制可变的状态,不如尽可能消灭可变的状态这个不确定因素。其立论的根据是这样的:假如语言不对外暴露那么多有出错可能的特性,那么开发者就不那么容易犯错。

  6. OOP的世界提倡开发者针对具体问题建立专门的数据结构,相关的专门操作以“方法”的形式附加在数据结构上。函数式编程语言实现重用的思路很不一样。函数式语言提倡在有限的几种关键数据结构(如list、set、map)上运用针对这些数据结构高度优化过的操作,以此构成基本的运转机构。开发者再根据具体用途,插入自己的数据结构和高阶函数去调整机构的运转方式。

  7. 比起一味创建新的类结构体系,把封装的单元降低到函数级别,更有利于达到细粒度的、基础层面的重用。

  8. 当垃圾收集成为主流,一下子将若干难以调试的错误类别连根拔起,程序员也因为运行时接管了复杂且容易出错的内存管理而获得解脱。函数式编程希望在算法编写上给予程序员同样的帮助,一方面程序员得以在更高的抽象层次上工作,另一方面运行时也有了执行复杂优化的自由空间。开发者从中获得的好处体现在更低的复杂性和更高的性能,这点与垃圾收集相同

  9. 命令式编程是按照“程序是一系列改变状态的命令”来建模的一种编程风格。传统的for循环是命令式风格的绝好例子:先确立初始状态,然后每次迭代都执行循环体中的一系列命令。

  10. 命令式编程鼓励程序员将操作安排在循环内部去执行。

package com.nealford.functionalthinking.trans;

import java.util.List;

public class TheCompanyProcess {
    public String cleanNames(List<String> listOfNames) {
        StringBuilder result = new StringBuilder();
        for(int i = 0; i < listOfNames.size(); i++) {
            if (listOfNames.get(i).length() > 1) {
                result.append(capitalizeString(listOfNames.get(i))).append(",");
            }
        }
        return result.substring(0, result.length() - 1).toString();
    }

    public String capitalizeString(String s) {
        return s.substring(0, 1).toUpperCase() + s.substring(1, s.length());
    }
}

// java8 函数式实现
public String cleanNames(List<String> names) {
    if (names == null) return "";
    return names
            .stream()
            .filter(name -> name.length() > 1)
            .map(name -> capitalize(name))
            .collect(Collectors.joining(","));
}

private String capitalize(String e) {
    return e.substring(0, 1).toUpperCase() + e.substring(1, e.length());
}
  1. 函数式编程将程序描述为表达式和变换,以数学方程的形式建立模型,并且尽量避免可变的状态。

listOfEmps
    -> filter(x.length > 1)
    -> transform(x.capitalize)
    -> convert(x + "," + y)

函数式语言可以帮助我们轻松搭建出上面的概念性解答模型,同时又不必操心各种实现细节。

向函数式思维靠拢,意味着我们逐渐学会何时何地应该求助于这些更高层次的抽象,不要再一头扎到实现细节里去。

  1. 函数式编程的主旨就是在使用函数的组合来实现运算。 这里,主体是各种函数之间的组合,以完成某种计算为目的,而输入的数据是不重要的。

  2. 组合函数,也是实现函数式编程 “无状态” 的过程。因为可以直接把一个函数的返回值作为参数传给下一个函数,避免中间状态。

学会用更高层次的抽象来思考有什么好处?

  1. 首先,会促使我们换一种角度去归类问题,看到问题的共性。

  2. 其次,让运行时有更大的余地去做智能的优化。有时候,在不改变最终输出的前提下,调整一下作业的先后次序会更有效率(例如减少了需要处理的条目)。

  3. 第三,让埋头于实现细节的开发者看到原本视野之外的一些解决方案。

例如上面的Java实现要改成多线程的话,需要的工作量可不小。由于我们自己控制着低层次的迭代细节,那么线程相关的代码也就只好由我们自己动手穿插进去。可是换作Scala的实现,我们只要在stream上多调用一次par方法就可以了。

val parallelResult = employees
  .par
  .filter(_.length() > 1)
  .map(_.capitalize)
  .reduce(_ + "," + _)

但Java 8(引入函数式编程的一些特性)实现要达到相同的并行化效果,也只需要做几乎一样的简单改动。

public String cleanNamesP(List<String> names) {
    if (names == null) return "";
    return names
            .parallelStream()
            .filter(n -> n.length() > 1)
            .map(e -> capitalize(e))
            .collect(Collectors.joining(","));
}

总结

  1. 我们在更高的抽象层次上做事情,运行时才好去优化低层次的细节。

  2. 多从结果着眼,少纠结具体的步骤。

  3. 不要再让那些迭代、变换、化约如何进行的低层次细节占据你的思维,多想想哪些问题其实可以归结为这几样基本操作的排列组合吧。

  4. 函数组合实现多个函数合并,最小功能的复用,多个小功能合成一个大功能,多个大功能再一起完成任务。

  5. 整洁架构中:实例可以用函数代替,用例就是多个函数组合来办一件事的函数

// 伪代码,不考虑细节,只考虑要做的事
// 上传文件->获取文件md5->检查是否上传等信息->开始上传->报告进度
uploadFiles(files).getFileMd5().checkUploadInfo().start().onProgressChange()


评论