函数式编程范式的意义在众多的编程范式中,大多数开发人员比较熟悉的是面向对象编程范式。一方面是由于面向对象编程语言比较流行,与之相关的资源比较丰富;另外一方面是由于大部分学校和培训机构的课程设置,都选择流行的面向对象编程语言。面向对象编程范式的优点在于其抽象方式与现实中的概念比较相近。比如,学生、课程、汽车和订单等这些现实中的概念,在抽象成相应的类之后,我们很容易就能理解类之间的关联关系。这些类中所包含的属性和方法可以很直观地设计出来。举例来说,学生所对应的类 Student 就应该有姓名、出生日期和性别等基本的属性,有方法可以获取到学生的年龄、所在的班级等信息。使用面向对象的编程思想,可以直观地在程序要处理的问题和程序本身之间,建立直接的对应关系。这种从问题域到解决域的简单对应关系,使得代码的可读性很强。对于初学者来说,这极大地降低了上手的难度。
函数式编程范式则相对较难理解。这主要是由于函数所代表的是抽象的计算,而不是具体的实体。因此比较难通过类比的方式来理解。举例来说,已知直角三角形的两条直角边的长度,需要通过计算来得到第三条边的长度。这种计算方式可以使用函数来表示。length(a, b)=√a²+b² 就是具体的计算方式。这样的计算方式与现实中的实体没有关联。
基于计算的抽象方式可以进一步提高代码的复用性。在一个学生信息管理系统中,可能会需要找到一个班级的某门课程的最高分数;在一个电子商务系统中,也可能会需要计算一个订单的总金额。看似风马牛不相及的两件事情,其实都包含了同样的计算在里面。也就是对一个可迭代的对象进行遍历,同时在遍历的过程中执行自定义的操作。在计算最高分数的场景中,在遍历的同时需要保存当前已知最高分数,并在遍历过程中更新该值;在计算订单总金额的场景中,在遍历的同时需要保存当前已累积的金额,并在遍历过程中更新该值。如果用 Java 代码来实现,可以很容易写出如下两段代码。清单 1 计算学生的最高分数。
清单 1. 计算学生的最高分数的代码1
2
3
4
5
6
| int maxMark = 0;
for (Student student : students) {
if (student.getMark() > maxMark) {
maxMark = student.getMark();
}
}
|
清单 2 计算订单的总金额。
清单 2. 计算订单的总金额的代码1
2
3
4
| BigDecimal total = BigDecimal.ZERO;
for (LineItem item : order.getLineItems()) {
total = total.add(item.getPrice().multiply(new BigDecimal(item.getCount())));
}
|
在面向对象编程的实现中,这两段代码会分别添加到课程和订单所对应的类的某个方法中。课程对应的类 Course 会有一个方法叫 getMaxMark,而订单对应的类 Order 会有一个方法叫 getTotal。尽管在实现上存在很多相似性和重复代码,由于课程和订单是两个完全不相关的概念,并没有办法通过面向对象中的继承或组合机制来提高代码复用和减少重复。而函数式编程可以很好地解决这个问题。
我们来进一步看一下清单 1 和清单 2 中的代码,尝试提取其中的计算模式。该计算模式由 3 个部分组成:
- 保存计算结果的状态,有初始值。
- 遍历操作。
- 遍历时进行的计算,更新保存计算结果的状态值。
把这 3 个元素提取出来,用伪代码表示,就得到了清单 3 中用函数表示的计算模式。iterable 表示被迭代的对象,updateValue 是遍历时进行的计算,initialValue 是初始值。
清单 3. 计算模式的伪代码1
2
3
4
5
6
7
| function(iterable, updateValue, initialValue) {
value = initialValue
loop(iterable) {
value = updateValue(value, currentValue)
}
return value
}
|
了解函数式编程的读者应该已经看出来了,这就是常用的 reduce 函数。使用 reduce 对清单 1 和清单 2 进行改写,可以得到清单 4 中的两段新的代码。
清单 4. 使用 reduce 函数改写代码1
2
3
4
5
6
7
8
| reduce(students, (mark, student) -> {
return Math.max(student.getMark(), mark);
}, 0);
reduce(order.lineItems, (total, item) -> {
return total.add(item.getPrice().multiply(new
BigDecimal(item.getCount())))
}, BigDecimal.ZERO);
|
|