理念
所有的函数都可以分为通用部分和非通用部分。通用部分每次调用都相同,非通用部分在不同的调用中可能有变化。通用部分就是函数体的静态代码,非通用部分通过参数来表达。如果把函数作为参数,那么函数所代表的算法逻辑,也可以作为非通用部分,从而整个函数的扩展性进一步增强,可以面向逻辑进行扩展。
Scala支持将函数作为参数传递,这给了我们更多优化代码的空间。把函数作为参数的函数,称为高阶函数。
实践
以一个文件搜索匹配的代码为例,优化之前的代码如下
def filesHere = new File("./src/main/scala").listFiles()
//查询已.scala结尾的文件
def filesEnding(query: String) = {
for (file <- filesHere if file.getName.endsWith(query))
yield file
}
//查询文件名包含关键字的
def filesContains(query: String) = {
for (file <- filesHere if file.getName.contains(query))
yield file
}
//按正则匹配查找文件
def filesRegex(query: String) = {
for (file <- filesHere if file.getName.matches(query))
yield file
}
可以发现,三个方法的大部分逻辑都是相同的,只有判断是否满足条件的逻辑有变化。因此,根据代码扩展的理念,我们接下来把这部分作为一个扩展点,用参数化的函数来表达,从而三个方法可以抽象成以下结构
def filesMatch(query: String) = {
for (file <- filesHere if method(file.getName,query))
yield file
}
其中的method就是我们要传入的函数参数,这个函数接收2个字符串,返回一个Boolean,具体的匹配逻辑,由每次传入的函数决定。因此,定一个一个通用的匹配框架代码,如下
//通用匹配代码
def filesMatch(query: String, isMatch: (String, String) => Boolean) = {
for (file <- filesHere; if isMatch(file.getName, query))
yield file
}
在不同的匹配方法中,只需要传入自己需要的匹配策略函数就可以了,因此,开始的3个匹配方法优化如下
def filesEnding(query: String) = {
filesMatch(query, _.endsWith(_))
}
def filesContains(query: String) = {
filesMatch(query, _.contains(_))
}
def filesRegex(query: String) = {
filesMatch(query, _.matches(_))
}
通过上述方法,实现了通用和变化部分的解耦,减少了重复代码,增加了扩展性。
在此基础上,代码还可以进一步简化。filesMatch函数的调用者已经知道了query的内容,可以把query从filesMatch的参数中移除,从而代码优化如下
//通用匹配代码
def filesMatch(isMatch: (String) => Boolean) = {
for (file <- filesHere; if isMatch(file.getName))
yield file
}
def filesEnding(query: String) = {
filesMatch(query, _.endsWith(query))
}
def filesContains(query: String) = {
filesMatch(query, _.contains(query))
}
def filesRegex(query: String) = {
filesMatch(query, _.matches(query))
}
//上面的_.endsWith(query)等函数,都是isMatch的字面量形式
这里就体现出闭包的作用,通过直接引用query这个自由变量,减少了1次参数传递。
小结
- 使用高阶函数优化代码的思路
- 找出重复和可变的代码
- 把可变部分的逻辑抽象成函数字面量表达式
- 使用的时候,以字面量的形式传入具体的函数