控制抽象之减少代码重复

scala没有太多的内建控制抽象,因为它提供给你的了创建自己的控制抽象的能力。前面已经学习了函数值,后面我们会展示给你如何把函数值应用到创建的控制抽象。首先来看看如何减少代码重复。

所有的函数都被分割成通用部分,以及非通用部分。通用部分函数体,而非通用部分必须由参数提供。当你把函数值用做参数时,算法的非通用部分就是它代表的某些其它算法。在这种函数的每一次调用中,你都可以把不同的函数值作为参数传入,于是被调用函数将在每次选用参数的时候调用传入的函数值。这种高阶函数带其它函数做参数的函数),给了你额外的机会去组织和简化代码。

高阶函数的一个好处是它们能让你创造控制抽象从而使你减少代码重复。例如,假设你正在写一个文件浏览器,并且你想要提供一个API,能够允许使用者搜索匹配某些标准的文件。首先,你加入了搜索文件名结束于特定字串的机制。这能让你的用户发现,比方说,所有扩展名为“.scala"的文件。你可以通过在单例对象中定义公开的filesEnding方法提供这样的API,如:

object FileMathcer {
    private def filesHere = (new java.io.File(".")).listFiles
    def filesEnding(query:String) = 
        for(file <- filesHere; if file.getName.endsWith(query)) yield file
}

目前为止还挺好,没有重复的代码。然而后来,你决定让别人可以基于文件名的任何部分做查询。这个功能可以良好地用于以下情况:你的用户记不住他们是以phb-important.doc,stupid-pub-report.doc,may2003salesdoc.phb,或什么完全不同的名字来命名文件的,但他们认为”phb“出现在文件的什么地方。你回来工作并把这个函数加到你的API,FileMatcher中:

def filesContaining(query:String) = 
    for(file <- filesHere; if file.getName.contains(query)) yield file

这段函数与filesEnding很像,唯一的差别是这个函数使用了contains替代endsWith。随着时间的推移,客户又想要基于正则表达式的搜索。为了支持它们,你又添加了这样一个函数:

def filesRegex(query:String) = 
    for(file <- filesHere; if file.getName.matches(query)) yield file

有经验的程序员这时就会注意到代码好像有些重复。如何能简化这些重复的代码呢?函数值给了答案。虽然你不能把方法名当作值传递,但你可以通过传递为你调用方法的函数值达到同样的效果。你可以给方法添加一个matcher参数,其唯一的目的就是针对查询检查文件名:

def filesMatching(query:String,matcher:(String,String) => Boolean) = {
    for(file <- filesHere; if matcher(file.getName,query)) yield file
}
def filesEnding(query:String) = filesMatching(query,_.endsWith(_))
def filesContaining(query:String) = filesMatching(query,_.contains(_))
def filesRegex(query:String) = filesMatching(query,_.matches(_))

这个例子中展示的函数文本使用了占位符语法,比如,用在filesEnding方法里的函数文本 _.endsWith(_),与下面的是一回事:

(fileName:String,query:String) => fileName.endsWith(query)

由于第一个参数fileName,在方法体中被第一个使用,第二个参数query,第二个使用,所以这里你也可以使用占位符语法:_.endsWith(_)。第一个下划线是第一个参数,文件名的占位符第二个下划线是第二个参数,查询字串的占位符

上面的代码已经比较简化了,但它实际还能更短。注意到query传递给了filesMatching,但filesMatching没有用query做任何事只是把它传回给传入的maatcher函数。这个传来传去的过程不是必需的,因为调用者在前面就已经知道了query的内容。我们可以再做一下简化,如下:

object FileMatcher {
    private def filesHere = (new java.io.File(".")).listFiles
    private def filesMatching(matcher:String => Boolean) =        //注意这里函数字面量
        for(file <- filesHere: if matcher(file.getName)) yield file
    def filesEnding(query:String) = filesMatching(_.endsWith(query))
    def filesContaining(query:String) = filesMatching(_.contains(query))
    def filesRegex(query:String) = filesMatching(_.matches(query)     
}

这个例子演示了函数作为头等值帮助你减少代码重复的方式,如果没有它们,这将变得很困难。再者,这个例子还演示了闭包是如何能帮助你减少代码重复的。前面一个例子里用到的函数字面量,如:_.endsWith(_)和_.contains(_),都是在运行期实例化成函数值而不是闭包,因为它们没有捕获任何自由变量。举例来说,表达式:_.endsWith(_)里用的两个变量,都是用下划线代表的,也就是说它们都是从传递给函数的参数获得的。因此,_.endsWith(_)使用了两个绑定变量,而不是自由变量。相对地,最近的例子里面用到的函数字面量_.endsWith(query)包含一个绑定变量,下划线代表的参数,和一个名为query的自由变量。仅仅因为scala支持闭包才使得你可以在最近的这个例子里从filesMatching中去掉query参数,从而更进一步简化了代码。

转载于:https://my.oschina.net/fhd/blog/277336

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值