7.8 对指令式代码进行重构
为了帮助你对函数式编程有更深的领悟,本节将对示例7.18的以指令式风格打印乘法表的做法进行重构。函数式风格版本如示例7.19所示。
示例7.19 用函数式编程的方式创建乘法表
示例7.18中的指令式风格体现在两个方面。首先,调用printMultiTable函数有一个副作用:将乘法表打印到标准输出中。在示例7.19中,对函数进行了重构,以字符串的形式返回乘法表。由于新的函数不再执行打印操作,因此我们将它重命名为multiTable。就像我们先前提到的,没有副作用的函数的优点之一是,更容易进行单元测试。要测试printMultiTable函数,需要以某种方式重新定义print和println,这样才能检查输出是否正确。而测试multiTable函数则更容易,只要检查它的字符串返回值即可。
其次,printMultiTable函数用到了while循环和var,这也是指令式风格的体现。相反地,multiTable函数用的是val、for表达式、助手函数(helper function)和对mkString方法的调用。
我们重构两个助手函数makeRow和makeRowSeq,让代码更易读。makeRowSeq函数使用for表达式的生成器遍历列号1到10。这个for表达式的执行体用于计算行号和列号的乘积,确定乘积需要的对齐补位,并交出将补位符和乘积拼接在一起的字符串结果。for表达式的结果将会是一个包含以这些交出的字符串作为元素的序列(scala.Seq的某个子类)。而另一个助手函数makeRow只是简单地对makeRowSeq函数调用mkString方法。mkString方法会把序列中的字符串拼接起来,返回整个字符串。
multiTable方法首先用一个for表达式的结果初始化tableSeq。这个for表达式的生成器会遍历1到10,对每个数调用makeRow函数得到对应行的字符串。这个字符串会被交出,因此这个for表达式的结果将会是包含了一行对应的字符串的序列。接下来就是将这个字符串序列转换成单个字符串了,调用mkString方法可以做到这一点。由于我们传入了"\n",因此在每两个字符串中间都插入了一个换行符。如果将multiTable返回的字符串传递给println,将会看到与调用printMultiTable函数相同的输出。